@stream-io/video-react-sdk 2.0.0-beta.0 → 2.0.0-beta.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 (328) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/css/embedded.css +2592 -1498
  3. package/dist/css/embedded.css.map +1 -1
  4. package/dist/css/styles.css +2388 -1222
  5. package/dist/css/styles.css.map +1 -1
  6. package/dist/embedded.cjs.js +535 -476
  7. package/dist/embedded.cjs.js.map +1 -1
  8. package/dist/esm/index.js +10 -7
  9. package/dist/esm/index.js.map +1 -1
  10. package/dist/esm/src/components/Avatar/Avatar.js +13 -5
  11. package/dist/esm/src/components/Avatar/Avatar.js.map +1 -1
  12. package/dist/esm/src/components/Badge/Badge.js +21 -0
  13. package/dist/esm/src/components/Badge/Badge.js.map +1 -0
  14. package/dist/esm/src/components/Button/Button.js +10 -0
  15. package/dist/esm/src/components/Button/Button.js.map +1 -0
  16. package/dist/esm/src/components/Button/CompositeButton.js +10 -17
  17. package/dist/esm/src/components/Button/CompositeButton.js.map +1 -1
  18. package/dist/esm/src/components/Button/IconButton.js +3 -9
  19. package/dist/esm/src/components/Button/IconButton.js.map +1 -1
  20. package/dist/esm/src/components/CallControls/AcceptCallButton.js +3 -2
  21. package/dist/esm/src/components/CallControls/AcceptCallButton.js.map +1 -1
  22. package/dist/esm/src/components/CallControls/CallControls.js +3 -0
  23. package/dist/esm/src/components/CallControls/CallControls.js.map +1 -1
  24. package/dist/esm/src/components/CallControls/CancelCallButton.js +11 -6
  25. package/dist/esm/src/components/CallControls/CancelCallButton.js.map +1 -1
  26. package/dist/esm/src/components/CallControls/ReactionsButton.js +7 -3
  27. package/dist/esm/src/components/CallControls/ReactionsButton.js.map +1 -1
  28. package/dist/esm/src/components/CallControls/RecordCallButton.js +17 -11
  29. package/dist/esm/src/components/CallControls/RecordCallButton.js.map +1 -1
  30. package/dist/esm/src/components/CallControls/ScreenShareButton.js +11 -4
  31. package/dist/esm/src/components/CallControls/ScreenShareButton.js.map +1 -1
  32. package/dist/esm/src/components/CallControls/ToggleAudioButton.js +28 -17
  33. package/dist/esm/src/components/CallControls/ToggleAudioButton.js.map +1 -1
  34. package/dist/esm/src/components/CallControls/ToggleAudioOutputButton.js +9 -4
  35. package/dist/esm/src/components/CallControls/ToggleAudioOutputButton.js.map +1 -1
  36. package/dist/esm/src/components/CallControls/ToggleVideoButton.js +32 -23
  37. package/dist/esm/src/components/CallControls/ToggleVideoButton.js.map +1 -1
  38. package/dist/esm/src/components/CallParticipantsList/BlockedUserListing.js +3 -3
  39. package/dist/esm/src/components/CallParticipantsList/BlockedUserListing.js.map +1 -1
  40. package/dist/esm/src/components/CallParticipantsList/CallParticipantListHeader.js +8 -3
  41. package/dist/esm/src/components/CallParticipantsList/CallParticipantListHeader.js.map +1 -1
  42. package/dist/esm/src/components/CallParticipantsList/CallParticipantListingItem.js +20 -7
  43. package/dist/esm/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -1
  44. package/dist/esm/src/components/CallParticipantsList/CallParticipantsList.js +8 -5
  45. package/dist/esm/src/components/CallParticipantsList/CallParticipantsList.js.map +1 -1
  46. package/dist/esm/src/components/CallParticipantsList/EmptyParticipantSearchList.js +5 -2
  47. package/dist/esm/src/components/CallParticipantsList/EmptyParticipantSearchList.js.map +1 -1
  48. package/dist/esm/src/components/CallRecordingList/CallRecordingListHeader.js +7 -3
  49. package/dist/esm/src/components/CallRecordingList/CallRecordingListHeader.js.map +1 -1
  50. package/dist/esm/src/components/DeviceSettings/DeviceAudioPreviewItem.js +1 -1
  51. package/dist/esm/src/components/DeviceSettings/DeviceAudioPreviewItem.js.map +1 -1
  52. package/dist/esm/src/components/DeviceSettings/DeviceSelector.js +1 -1
  53. package/dist/esm/src/components/DeviceSettings/DeviceSelector.js.map +1 -1
  54. package/dist/esm/src/components/DeviceSettings/DeviceSelectorAudio.js +2 -2
  55. package/dist/esm/src/components/DeviceSettings/DeviceSelectorAudio.js.map +1 -1
  56. package/dist/esm/src/components/DeviceSettings/DeviceSettings.js +8 -5
  57. package/dist/esm/src/components/DeviceSettings/DeviceSettings.js.map +1 -1
  58. package/dist/esm/src/components/DeviceSettings/SpeakerTest.js +10 -4
  59. package/dist/esm/src/components/DeviceSettings/SpeakerTest.js.map +1 -1
  60. package/dist/esm/src/components/DropdownSelect/DropdownSelect.js +1 -1
  61. package/dist/esm/src/components/DropdownSelect/DropdownSelect.js.map +1 -1
  62. package/dist/esm/src/components/Menu/GenericMenu.js +2 -1
  63. package/dist/esm/src/components/Menu/GenericMenu.js.map +1 -1
  64. package/dist/esm/src/components/Menu/MenuToggle.js +3 -0
  65. package/dist/esm/src/components/Menu/MenuToggle.js.map +1 -1
  66. package/dist/esm/src/components/Notification/MicCaptureErrorNotification.js +6 -3
  67. package/dist/esm/src/components/Notification/MicCaptureErrorNotification.js.map +1 -1
  68. package/dist/esm/src/components/Notification/Notification.js +12 -3
  69. package/dist/esm/src/components/Notification/Notification.js.map +1 -1
  70. package/dist/esm/src/components/Notification/PermissionNotification.js +2 -2
  71. package/dist/esm/src/components/Notification/PermissionNotification.js.map +1 -1
  72. package/dist/esm/src/components/Notification/RecordingInProgressNotification.js +7 -3
  73. package/dist/esm/src/components/Notification/RecordingInProgressNotification.js.map +1 -1
  74. package/dist/esm/src/components/Notification/SpeakingWhileMutedNotification.js +7 -2
  75. package/dist/esm/src/components/Notification/SpeakingWhileMutedNotification.js.map +1 -1
  76. package/dist/esm/src/components/Permissions/PermissionRequests.js +13 -12
  77. package/dist/esm/src/components/Permissions/PermissionRequests.js.map +1 -1
  78. package/dist/esm/src/components/RingingCall/RingingCall.js +38 -14
  79. package/dist/esm/src/components/RingingCall/RingingCall.js.map +1 -1
  80. package/dist/esm/src/components/RingingCall/RingingCallControls.js +7 -3
  81. package/dist/esm/src/components/RingingCall/RingingCallControls.js.map +1 -1
  82. package/dist/esm/src/components/Search/SearchInput.js +1 -1
  83. package/dist/esm/src/components/Search/SearchInput.js.map +1 -1
  84. package/dist/esm/src/components/StreamTheme/StreamTheme.js +2 -2
  85. package/dist/esm/src/components/StreamTheme/StreamTheme.js.map +1 -1
  86. package/dist/esm/src/components/Tooltip/Tooltip.js +8 -8
  87. package/dist/esm/src/components/Tooltip/Tooltip.js.map +1 -1
  88. package/dist/esm/src/components/VideoPreview/VideoPreview.js +36 -5
  89. package/dist/esm/src/components/VideoPreview/VideoPreview.js.map +1 -1
  90. package/dist/esm/src/core/components/CallLayout/LivestreamLayout.js +38 -12
  91. package/dist/esm/src/core/components/CallLayout/LivestreamLayout.js.map +1 -1
  92. package/dist/esm/src/core/components/CallLayout/PaginatedGridLayout.js +9 -5
  93. package/dist/esm/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -1
  94. package/dist/esm/src/core/components/CallLayout/PipLayout/Grid.js +9 -6
  95. package/dist/esm/src/core/components/CallLayout/PipLayout/Grid.js.map +1 -1
  96. package/dist/esm/src/core/components/CallLayout/PipLayout/Pip.js +9 -6
  97. package/dist/esm/src/core/components/CallLayout/PipLayout/Pip.js.map +1 -1
  98. package/dist/esm/src/core/components/CallLayout/SpeakerLayout.js +11 -6
  99. package/dist/esm/src/core/components/CallLayout/SpeakerLayout.js.map +1 -1
  100. package/dist/esm/src/core/components/ParticipantView/ConnectionQualityIndicator.js +25 -0
  101. package/dist/esm/src/core/components/ParticipantView/ConnectionQualityIndicator.js.map +1 -0
  102. package/dist/esm/src/core/components/ParticipantView/DefaultParticipantViewUI.js +17 -13
  103. package/dist/esm/src/core/components/ParticipantView/DefaultParticipantViewUI.js.map +1 -1
  104. package/dist/esm/src/core/components/ParticipantView/ParticipantActionsContextMenu.js +13 -8
  105. package/dist/esm/src/core/components/ParticipantView/ParticipantActionsContextMenu.js.map +1 -1
  106. package/dist/esm/src/core/components/StreamVideo/StreamVideo.js +24 -3
  107. package/dist/esm/src/core/components/StreamVideo/StreamVideo.js.map +1 -1
  108. package/dist/esm/src/core/components/Video/BaseVideoPlaceholder.js +3 -13
  109. package/dist/esm/src/core/components/Video/BaseVideoPlaceholder.js.map +1 -1
  110. package/dist/esm/src/core/components/Video/DefaultPictureInPicturePlaceholder.js +4 -2
  111. package/dist/esm/src/core/components/Video/DefaultPictureInPicturePlaceholder.js.map +1 -1
  112. package/dist/esm/src/core/components/Video/DefaultVideoPlaceholder.js +4 -2
  113. package/dist/esm/src/core/components/Video/DefaultVideoPlaceholder.js.map +1 -1
  114. package/dist/esm/src/embedded/EmbeddedClientProvider.js +7 -4
  115. package/dist/esm/src/embedded/EmbeddedClientProvider.js.map +1 -1
  116. package/dist/esm/src/embedded/call/CallControls.js +10 -6
  117. package/dist/esm/src/embedded/call/CallControls.js.map +1 -1
  118. package/dist/esm/src/embedded/call/CallHeader.js +10 -7
  119. package/dist/esm/src/embedded/call/CallHeader.js.map +1 -1
  120. package/dist/esm/src/embedded/call/CallLayout.js +8 -5
  121. package/dist/esm/src/embedded/call/CallLayout.js.map +1 -1
  122. package/dist/esm/src/embedded/call/CallStateRouter.js +7 -4
  123. package/dist/esm/src/embedded/call/CallStateRouter.js.map +1 -1
  124. package/dist/esm/src/embedded/hooks/useLayout.js +7 -4
  125. package/dist/esm/src/embedded/hooks/useLayout.js.map +1 -1
  126. package/dist/esm/src/embedded/livestream/host/HostLayout.js +12 -7
  127. package/dist/esm/src/embedded/livestream/host/HostLayout.js.map +1 -1
  128. package/dist/esm/src/embedded/livestream/host/HostStateRouter.js +12 -7
  129. package/dist/esm/src/embedded/livestream/host/HostStateRouter.js.map +1 -1
  130. package/dist/esm/src/embedded/livestream/viewer/ViewerLayout.js +9 -6
  131. package/dist/esm/src/embedded/livestream/viewer/ViewerLayout.js.map +1 -1
  132. package/dist/esm/src/embedded/livestream/viewer/ViewerLobby.js +12 -9
  133. package/dist/esm/src/embedded/livestream/viewer/ViewerLobby.js.map +1 -1
  134. package/dist/esm/src/embedded/livestream/viewer/ViewerStateRouter.js +7 -4
  135. package/dist/esm/src/embedded/livestream/viewer/ViewerStateRouter.js.map +1 -1
  136. package/dist/esm/src/embedded/shared/BlurToggleButton/BlurToggleButton.js +13 -9
  137. package/dist/esm/src/embedded/shared/BlurToggleButton/BlurToggleButton.js.map +1 -1
  138. package/dist/esm/src/embedded/shared/CallDuration/CallDuration.js +40 -0
  139. package/dist/esm/src/embedded/shared/CallDuration/CallDuration.js.map +1 -0
  140. package/dist/esm/src/embedded/shared/CallFeedback/CallEndedScreen.js +8 -6
  141. package/dist/esm/src/embedded/shared/CallFeedback/CallEndedScreen.js.map +1 -1
  142. package/dist/esm/src/embedded/shared/CallFeedback/RatingScreen.js +26 -2
  143. package/dist/esm/src/embedded/shared/CallFeedback/RatingScreen.js.map +1 -1
  144. package/dist/esm/src/embedded/shared/CallFeedback/StarRating.js +12 -6
  145. package/dist/esm/src/embedded/shared/CallFeedback/StarRating.js.map +1 -1
  146. package/dist/esm/src/embedded/shared/CallFeedback/ThankYouScreen.js +8 -6
  147. package/dist/esm/src/embedded/shared/CallFeedback/ThankYouScreen.js.map +1 -1
  148. package/dist/esm/src/embedded/shared/ConnectionNotification/ConnectionNotification.js +15 -14
  149. package/dist/esm/src/embedded/shared/ConnectionNotification/ConnectionNotification.js.map +1 -1
  150. package/dist/esm/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.js +4 -0
  151. package/dist/esm/src/embedded/shared/EmbeddedParticipantViewUI/EmbeddedParticipantViewUI.js.map +1 -1
  152. package/dist/esm/src/embedded/shared/JoinError/JoinError.js +8 -6
  153. package/dist/esm/src/embedded/shared/JoinError/JoinError.js.map +1 -1
  154. package/dist/esm/src/embedded/shared/Lobby/DeviceControls.js +11 -7
  155. package/dist/esm/src/embedded/shared/Lobby/DeviceControls.js.map +1 -1
  156. package/dist/esm/src/embedded/shared/Lobby/DisabledDeviceButton.js +6 -4
  157. package/dist/esm/src/embedded/shared/Lobby/DisabledDeviceButton.js.map +1 -1
  158. package/dist/esm/src/embedded/shared/Lobby/Lobby.js +10 -9
  159. package/dist/esm/src/embedded/shared/Lobby/Lobby.js.map +1 -1
  160. package/dist/esm/src/embedded/shared/Lobby/ToggleCameraButton.js +6 -4
  161. package/dist/esm/src/embedded/shared/Lobby/ToggleCameraButton.js.map +1 -1
  162. package/dist/esm/src/embedded/shared/Lobby/ToggleMicButton.js +8 -6
  163. package/dist/esm/src/embedded/shared/Lobby/ToggleMicButton.js.map +1 -1
  164. package/dist/esm/src/embedded/shared/Lobby/VideoPreviewFallbacks.js +11 -9
  165. package/dist/esm/src/embedded/shared/Lobby/VideoPreviewFallbacks.js.map +1 -1
  166. package/dist/esm/src/embedded/shared/ViewersCount/ViewersCount.js +6 -4
  167. package/dist/esm/src/embedded/shared/ViewersCount/ViewersCount.js.map +1 -1
  168. package/dist/esm/src/hooks/useDeviceList.js +4 -2
  169. package/dist/esm/src/hooks/useDeviceList.js.map +1 -1
  170. package/dist/esm/src/i18n/Streami18n.js +58 -0
  171. package/dist/esm/src/i18n/Streami18n.js.map +1 -0
  172. package/dist/esm/src/i18n/TranslationContext.js +42 -0
  173. package/dist/esm/src/i18n/TranslationContext.js.map +1 -0
  174. package/dist/esm/src/i18n/runtimeDefaults.js +20 -0
  175. package/dist/esm/src/i18n/runtimeDefaults.js.map +1 -0
  176. package/dist/esm/src/i18n/useStreami18n.js +49 -0
  177. package/dist/esm/src/i18n/useStreami18n.js.map +1 -0
  178. package/dist/esm/src/wrappers/LivestreamPlayer/LivestreamPlayer.js +4 -0
  179. package/dist/esm/src/wrappers/LivestreamPlayer/LivestreamPlayer.js.map +1 -1
  180. package/dist/index.cjs.js +559 -623
  181. package/dist/index.cjs.js.map +1 -1
  182. package/dist/index.d.ts +1 -1
  183. package/dist/src/components/Avatar/Avatar.d.ts +9 -4
  184. package/dist/src/components/Badge/Badge.d.ts +23 -0
  185. package/dist/src/components/Badge/index.d.ts +1 -0
  186. package/dist/src/components/Button/Button.d.ts +16 -0
  187. package/dist/src/components/Button/CompositeButton.d.ts +6 -3
  188. package/dist/src/components/Button/IconButton.d.ts +7 -7
  189. package/dist/src/components/Button/index.d.ts +2 -2
  190. package/dist/src/components/CallControls/CancelCallButton.d.ts +3 -1
  191. package/dist/src/components/CallControls/ToggleAudioButton.d.ts +3 -3
  192. package/dist/src/components/CallControls/ToggleAudioOutputButton.d.ts +2 -2
  193. package/dist/src/components/CallControls/ToggleVideoButton.d.ts +3 -3
  194. package/dist/src/components/CallControls/index.d.ts +0 -1
  195. package/dist/src/components/Menu/GenericMenu.d.ts +1 -0
  196. package/dist/src/components/Notification/Notification.d.ts +2 -1
  197. package/dist/src/components/StreamTheme/StreamTheme.d.ts +2 -1
  198. package/dist/src/components/VideoPreview/VideoPreview.d.ts +6 -1
  199. package/dist/src/components/index.d.ts +1 -1
  200. package/dist/src/core/components/ParticipantView/ConnectionQualityIndicator.d.ts +10 -0
  201. package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
  202. package/dist/src/core/components/StreamVideo/StreamVideo.d.ts +18 -2
  203. package/dist/src/embedded/shared/CallDuration/CallDuration.d.ts +9 -0
  204. package/dist/src/embedded/shared/index.d.ts +1 -0
  205. package/dist/src/i18n/Streami18n.d.ts +56 -0
  206. package/dist/src/i18n/TranslationContext.d.ts +30 -0
  207. package/dist/src/i18n/index.d.ts +16 -0
  208. package/dist/src/i18n/keys.d.ts +164 -0
  209. package/dist/src/i18n/runtimeDefaults.d.ts +17 -0
  210. package/dist/src/i18n/types.d.ts +45 -0
  211. package/dist/src/i18n/useStreami18n.d.ts +19 -0
  212. package/index.ts +1 -1
  213. package/package.json +18 -13
  214. package/src/components/Avatar/Avatar.tsx +53 -19
  215. package/src/components/Badge/Badge.tsx +48 -0
  216. package/src/components/Badge/index.ts +1 -0
  217. package/src/components/Button/Button.tsx +49 -0
  218. package/src/components/Button/CompositeButton.tsx +82 -73
  219. package/src/components/Button/IconButton.tsx +19 -29
  220. package/src/components/Button/index.ts +2 -2
  221. package/src/components/CallControls/AcceptCallButton.tsx +3 -1
  222. package/src/components/CallControls/CancelCallButton.tsx +32 -24
  223. package/src/components/CallControls/ReactionsButton.tsx +7 -3
  224. package/src/components/CallControls/RecordCallButton.tsx +50 -23
  225. package/src/components/CallControls/ScreenShareButton.tsx +27 -6
  226. package/src/components/CallControls/ToggleAudioButton.tsx +62 -61
  227. package/src/components/CallControls/ToggleAudioOutputButton.tsx +7 -4
  228. package/src/components/CallControls/ToggleVideoButton.tsx +66 -66
  229. package/src/components/CallControls/index.ts +0 -1
  230. package/src/components/CallParticipantsList/BlockedUserListing.tsx +9 -2
  231. package/src/components/CallParticipantsList/CallParticipantListHeader.tsx +10 -3
  232. package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +33 -8
  233. package/src/components/CallParticipantsList/CallParticipantsList.tsx +21 -5
  234. package/src/components/CallParticipantsList/EmptyParticipantSearchList.tsx +2 -2
  235. package/src/components/CallRecordingList/CallRecordingListHeader.tsx +11 -3
  236. package/src/components/DeviceSettings/DeviceAudioPreviewItem.tsx +3 -1
  237. package/src/components/DeviceSettings/DeviceSelector.tsx +1 -1
  238. package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +2 -7
  239. package/src/components/DeviceSettings/DeviceSettings.tsx +14 -5
  240. package/src/components/DeviceSettings/SpeakerTest.tsx +7 -2
  241. package/src/components/DropdownSelect/DropdownSelect.tsx +3 -1
  242. package/src/components/Menu/GenericMenu.tsx +4 -0
  243. package/src/components/Notification/MicCaptureErrorNotification.tsx +7 -2
  244. package/src/components/Notification/Notification.tsx +20 -4
  245. package/src/components/Notification/PermissionNotification.tsx +2 -0
  246. package/src/components/Notification/RecordingInProgressNotification.tsx +5 -6
  247. package/src/components/Notification/SpeakingWhileMutedNotification.tsx +8 -2
  248. package/src/components/Permissions/PermissionRequests.tsx +50 -43
  249. package/src/components/RingingCall/RingingCall.tsx +40 -14
  250. package/src/components/RingingCall/RingingCallControls.tsx +2 -1
  251. package/src/components/Search/SearchInput.tsx +2 -3
  252. package/src/components/StreamTheme/StreamTheme.tsx +3 -1
  253. package/src/components/Tooltip/Tooltip.tsx +3 -10
  254. package/src/components/VideoPreview/VideoPreview.tsx +50 -5
  255. package/src/components/index.ts +1 -1
  256. package/src/core/components/CallLayout/LivestreamLayout.tsx +48 -18
  257. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +14 -0
  258. package/src/core/components/CallLayout/PipLayout/Grid.tsx +10 -2
  259. package/src/core/components/CallLayout/PipLayout/Pip.tsx +6 -2
  260. package/src/core/components/CallLayout/SpeakerLayout.tsx +27 -0
  261. package/src/core/components/CallLayout/partcipantFilter.test.ts +38 -31
  262. package/src/core/components/ParticipantView/ConnectionQualityIndicator.tsx +54 -0
  263. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +60 -27
  264. package/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx +63 -22
  265. package/src/core/components/ParticipantView/index.ts +1 -0
  266. package/src/core/components/StreamVideo/StreamVideo.tsx +39 -4
  267. package/src/core/components/Video/BaseVideoPlaceholder.tsx +13 -30
  268. package/src/core/components/Video/DefaultPictureInPicturePlaceholder.tsx +5 -2
  269. package/src/core/components/Video/DefaultVideoPlaceholder.tsx +2 -2
  270. package/src/embedded/call/CallControls.tsx +9 -18
  271. package/src/embedded/call/CallHeader.tsx +3 -12
  272. package/src/embedded/call/CallLayout.tsx +1 -1
  273. package/src/embedded/livestream/host/HostLayout.tsx +26 -20
  274. package/src/embedded/livestream/host/HostStateRouter.tsx +12 -8
  275. package/src/embedded/livestream/viewer/ViewerLayout.tsx +8 -11
  276. package/src/embedded/livestream/viewer/ViewerLobby.tsx +31 -15
  277. package/src/embedded/shared/BlurToggleButton/BlurToggleButton.tsx +18 -12
  278. package/src/embedded/shared/CallDuration/CallDuration.tsx +32 -0
  279. package/src/embedded/shared/CallFeedback/CallEndedScreen.tsx +27 -13
  280. package/src/embedded/shared/CallFeedback/RatingScreen.tsx +20 -12
  281. package/src/embedded/shared/CallFeedback/StarRating.tsx +10 -3
  282. package/src/embedded/shared/CallFeedback/ThankYouScreen.tsx +6 -3
  283. package/src/embedded/shared/ConnectionNotification/ConnectionNotification.tsx +21 -14
  284. package/src/embedded/shared/JoinError/JoinError.tsx +7 -6
  285. package/src/embedded/shared/Lobby/DeviceControls.tsx +25 -3
  286. package/src/embedded/shared/Lobby/Lobby.tsx +19 -22
  287. package/src/embedded/shared/Lobby/ToggleMicButton.tsx +10 -3
  288. package/src/embedded/shared/Lobby/VideoPreviewFallbacks.tsx +4 -2
  289. package/src/embedded/shared/index.ts +1 -0
  290. package/src/hooks/useDeviceList.tsx +2 -2
  291. package/src/i18n/Streami18n.ts +67 -0
  292. package/src/i18n/TranslationContext.tsx +55 -0
  293. package/src/i18n/index.ts +22 -0
  294. package/src/i18n/keys.ts +169 -0
  295. package/src/i18n/runtimeDefaults.ts +17 -0
  296. package/src/i18n/types.ts +78 -0
  297. package/src/i18n/useStreami18n.ts +66 -0
  298. package/src/utilities/filter.test.ts +132 -124
  299. package/src/utilities/normalizeString.test.ts +50 -48
  300. package/dist/embedded-CallStatsLatencyChart-CpL1M_s0.cjs.js +0 -59
  301. package/dist/embedded-CallStatsLatencyChart-CpL1M_s0.cjs.js.map +0 -1
  302. package/dist/esm/src/components/Button/TextButton.js +0 -8
  303. package/dist/esm/src/components/Button/TextButton.js.map +0 -1
  304. package/dist/esm/src/components/CallControls/CallStatsButton.js +0 -18
  305. package/dist/esm/src/components/CallControls/CallStatsButton.js.map +0 -1
  306. package/dist/esm/src/components/CallStats/CallStats.js +0 -180
  307. package/dist/esm/src/components/CallStats/CallStats.js.map +0 -1
  308. package/dist/esm/src/components/CallStats/CallStatsLatencyChart.js +0 -57
  309. package/dist/esm/src/components/CallStats/CallStatsLatencyChart.js.map +0 -1
  310. package/dist/esm/src/translations/en.json.js +0 -177
  311. package/dist/esm/src/translations/en.json.js.map +0 -1
  312. package/dist/esm/src/translations/index.js +0 -6
  313. package/dist/esm/src/translations/index.js.map +0 -1
  314. package/dist/latency-chart-CpL1M_s0.cjs.js +0 -59
  315. package/dist/latency-chart-CpL1M_s0.cjs.js.map +0 -1
  316. package/dist/src/components/Button/TextButton.d.ts +0 -2
  317. package/dist/src/components/CallControls/CallStatsButton.d.ts +0 -4
  318. package/dist/src/components/CallStats/CallStats.d.ts +0 -22
  319. package/dist/src/components/CallStats/CallStatsLatencyChart.d.ts +0 -7
  320. package/dist/src/components/CallStats/index.d.ts +0 -1
  321. package/dist/src/translations/index.d.ts +0 -146
  322. package/src/components/Button/TextButton.tsx +0 -12
  323. package/src/components/CallControls/CallStatsButton.tsx +0 -37
  324. package/src/components/CallStats/CallStats.tsx +0 -439
  325. package/src/components/CallStats/CallStatsLatencyChart.tsx +0 -76
  326. package/src/components/CallStats/index.ts +0 -1
  327. package/src/translations/en.json +0 -154
  328. package/src/translations/index.ts +0 -3
package/dist/index.cjs.js CHANGED
@@ -4,7 +4,9 @@ var videoClient = require('@stream-io/video-client');
4
4
  var videoReactBindings = require('@stream-io/video-react-bindings');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var react = require('react');
7
- var react$1 = require('@floating-ui/react');
7
+ var i18n = require('@stream-io/i18n');
8
+ var react$1 = require('@stream-io/i18n/react');
9
+ var react$2 = require('@floating-ui/react');
8
10
  var clsx = require('clsx');
9
11
 
10
12
  const Audio = react.memo(function AudioRender({ participant, trackType = 'audioTrack', ...rest }) {
@@ -39,18 +41,169 @@ const ParticipantsAudio = (props) => {
39
41
  };
40
42
  ParticipantsAudio.displayName = 'ParticipantsAudio';
41
43
 
44
+ /**
45
+ * The SDK's bundled translation data: the keys that cannot carry an inline `defaultValue` at their
46
+ * call site. `Streami18n` layers it under *every* language, so a partial dictionary never knocks
47
+ * these out.
48
+ *
49
+ * Empty, and that is the intended steady state. The four lookups this SDK resolved from a runtime
50
+ * value — the only candidates for an entry here — are written as `switch` statements over literal
51
+ * `t()` calls instead, so every branch carries its own English copy inline and the codegen reads it
52
+ * straight off the call site. Nothing is left without a default. `timestamp.*` / `duration.*`
53
+ * formatter expressions would also live here; this SDK has none yet.
54
+ *
55
+ * Adding an entry later needs no wiring. `Streami18n` already spreads this into the core options and
56
+ * `scripts/generate-i18n-keys.mts` already reads this path, so a new key reaches `keys.ts` on the
57
+ * next `build-translations` run — where a key nothing references, or one that shadows an inline
58
+ * default, fails a codegen guard.
59
+ */
60
+ const runtimeDefaults = {};
61
+
62
+ /**
63
+ * Wrapper around [i18next](https://www.i18next.com/) for this SDK's translations. Pass an instance
64
+ * to `<StreamVideo i18nInstance={…}>` to control the language and the copy.
65
+ *
66
+ * The implementation lives in `@stream-io/i18n`, shared with the React Native SDK and with Chat.
67
+ * What this subclass adds is what is this package's own: its bundled translation data, and the two
68
+ * type parameters — core cannot supply either, because the key catalog is generated from *this*
69
+ * package's `t()` call sites.
70
+ *
71
+ * ## Overriding some of the English copy
72
+ *
73
+ * ```ts
74
+ * const i18n = new Streami18n({
75
+ * translationsForLanguage: {
76
+ * 'common.participants.label': 'People',
77
+ * },
78
+ * });
79
+ * ```
80
+ *
81
+ * ## Adding a language
82
+ *
83
+ * ```ts
84
+ * import 'dayjs/locale/nl';
85
+ *
86
+ * const i18n = new Streami18n({ language: 'nl' });
87
+ * i18n.registerTranslation('nl', {
88
+ * 'callControls.cancelCallButton.leaveCall.title': 'Verlaten',
89
+ * });
90
+ * ```
91
+ *
92
+ * No dayjs locale file defines `calendar` — that field belongs to the calendar plugin — so a
93
+ * language whose relative dates must read natively needs the locale import *and* a `calendar`
94
+ * config passed as the third argument.
95
+ *
96
+ * Type your dictionary as `TranslationDictionary` to turn a typo or a key removed in a later major
97
+ * into a compile error. A **partial dictionary is safe**: any key you leave out renders the English
98
+ * copy that ships inline at each call site, never a raw dotted path — so there is no need to
99
+ * translate everything before shipping a language.
100
+ *
101
+ * Reactivity goes through `i18n.state`, a `StateStore`. `setLanguage()` returns nothing — the new
102
+ * `t` is published to that store, which the provider subscribes to.
103
+ */
104
+ class Streami18n extends i18n.Streami18n {
105
+ constructor(options = {}) {
106
+ super({
107
+ ...options,
108
+ // Merged, not replaced. Spreading `options` over a literal would let an integrator supplying
109
+ // one entry silently drop the SDK's own bundled data, which then renders as raw keys.
110
+ runtimeDefaults: { ...runtimeDefaults, ...options.runtimeDefaults },
111
+ });
112
+ }
113
+ }
114
+
115
+ /**
116
+ * The `t` in force before i18next has initialized, and the context's default.
117
+ *
118
+ * Core's factory, instantiated against this SDK's catalog so it is typed exactly like the real `t`.
119
+ * It honours the inline `defaultValue` each prose call site passes, which is what stops raw dotted
120
+ * keys flashing on the first frame.
121
+ */
122
+ const defaultTranslatorFunction = i18n.createDefaultTranslatorFunction();
123
+ /**
124
+ * Built from the shared factory in `@stream-io/i18n/react`.
125
+ *
126
+ * A **default value** is supplied rather than making the hook throw, and that is deliberate: it
127
+ * matches what `useI18n` did before this module existed, and it is what lets the `embedded/`
128
+ * components — which are used standalone, outside any provider — render correct English instead of
129
+ * crashing. Chat's React Native SDK throws instead; hence core exposes this as a factory.
130
+ */
131
+ const { TranslationContext, TranslationProvider, useTranslationContext } = react$1.createTranslationContext({
132
+ defaultValue: {
133
+ t: defaultTranslatorFunction,
134
+ tDateTimeParser: i18n.defaultDateTimeParser,
135
+ },
136
+ displayName: 'StreamVideoTranslationContext',
137
+ });
138
+ /**
139
+ * Reads `t` and `tDateTimeParser` from the closest provider.
140
+ *
141
+ * Works outside a provider too — the default translator renders each call site's inline English.
142
+ *
143
+ * The name is `useI18n` rather than core's `useTranslationContext` because ~500 call sites in this
144
+ * SDK already import it; renaming them is churn with no payoff.
145
+ *
146
+ * @example
147
+ * const { t } = useI18n();
148
+ * const label = t('callControls.cancelCallButton.leaveCall.title', 'Leave call');
149
+ */
150
+ const useI18n = useTranslationContext;
151
+
152
+ /**
153
+ * Resolves the translation context value: creates or adopts a {@link Streami18n} instance,
154
+ * registers the supplied dictionaries, initializes it and keeps `t` fresh across language changes.
155
+ *
156
+ * `init()` and the store subscription belong to `useStreami18nState` in `@stream-io/i18n/react`,
157
+ * shared with the React Native SDK.
158
+ */
159
+ const useStreami18n = ({ i18nInstance, language, translations, }) => {
160
+ // Lazy `useState`, not `useMemo`: `new Streami18n()` is a real allocation with identity that must
161
+ // survive a re-render, and this repo lints `react-hooks/purity` as an error — `useMemo` is a
162
+ // cache React is free to drop, so constructing there is a purity violation. A later change to
163
+ // `i18nInstance` is ignored on purpose; swapping the instance mid-tree is not a supported move.
164
+ const [i18n] = react.useState(() => {
165
+ const instance = i18nInstance ?? new Streami18n({ language });
166
+ // Registered here rather than in an effect. `useStreami18nState` calls `init()` from its own
167
+ // effect, which runs *before* any effect declared below it in this hook — so a dictionary
168
+ // registered in an effect would arrive after i18next was already initialized, and the first
169
+ // `init()` would warn that the language has no dictionary.
170
+ if (translations) {
171
+ for (const [code, dictionary] of Object.entries(translations)) {
172
+ instance.registerTranslation(code, dictionary);
173
+ }
174
+ }
175
+ return instance;
176
+ });
177
+ // No `onInitError` handler: core already logs a warning and leaves the instance usable, with `t`
178
+ // still rendering each call site's inline English. Pass one only to surface init failures
179
+ // somewhere other than the console.
180
+ const translators = react$1.useStreami18nState(i18n);
181
+ react.useEffect(() => {
182
+ // Guarded on the current value: `setLanguage` re-runs the formatter factories and a full
183
+ // i18next `changeLanguage`, and the first render already constructed the instance with
184
+ // `language`. Undefined means "leave whatever the instance was configured with".
185
+ if (!language || i18n.currentLanguage === language)
186
+ return;
187
+ i18n.setLanguage(language);
188
+ }, [i18n, language]);
189
+ // Returned as-is: `useStreami18nState` already caches this object and only produces a new one
190
+ // when `t` or `tDateTimeParser` actually changes, so wrapping it in a `useMemo` would add a
191
+ // second cache over a stable value.
192
+ return translators;
193
+ };
194
+
42
195
  const ParticipantViewContext = react.createContext(undefined);
43
196
  const useParticipantViewContext = () => react.useContext(ParticipantViewContext);
44
197
 
45
198
  const useFloatingUIPreset = ({ middleware = [], placement, strategy, offset: offsetInPx = 10, }) => {
46
- const { refs, x, y, update, elements: { domReference, floating }, context, } = react$1.useFloating({
199
+ const { refs, x, y, update, elements: { domReference, floating }, context, } = react$2.useFloating({
47
200
  placement,
48
201
  strategy,
49
202
  middleware: [
50
- react$1.offset(offsetInPx),
51
- react$1.shift(),
52
- react$1.flip(),
53
- react$1.size({
203
+ react$2.offset(offsetInPx),
204
+ react$2.shift(),
205
+ react$2.flip(),
206
+ react$2.size({
54
207
  padding: 10,
55
208
  apply: ({ availableHeight, elements }) => {
56
209
  Object.assign(elements.floating.style, {
@@ -65,7 +218,7 @@ const useFloatingUIPreset = ({ middleware = [], placement, strategy, offset: off
65
218
  react.useEffect(() => {
66
219
  if (!domReference || !floating)
67
220
  return;
68
- const cleanup = react$1.autoUpdate(domReference, floating, update);
221
+ const cleanup = react$2.autoUpdate(domReference, floating, update);
69
222
  return () => cleanup();
70
223
  }, [domReference, floating, update]);
71
224
  return { refs, x, y, domReference, floating, strategy, context };
@@ -189,7 +342,7 @@ const useRequestPermission = (permission) => {
189
342
  * 2. Maps the device list to a format more suitable for rendering.
190
343
  */
191
344
  function useDeviceList(devices, selectedDeviceId) {
192
- const { t } = videoReactBindings.useI18n();
345
+ const { t } = useI18n();
193
346
  return react.useMemo(() => {
194
347
  let selectedDeviceInfo = null;
195
348
  let selectedIndex = null;
@@ -205,7 +358,7 @@ function useDeviceList(devices, selectedDeviceId) {
205
358
  if (selectedDeviceInfo === null || selectedIndex === null) {
206
359
  const defaultDevice = {
207
360
  deviceId: 'default',
208
- label: t('Default'),
361
+ label: t('common.default.label', 'Default'),
209
362
  isSelected: true,
210
363
  };
211
364
  selectedDeviceInfo = defaultDevice;
@@ -607,7 +760,7 @@ const useMenuContext = () => {
607
760
  };
608
761
  const MenuPortal = ({ children, refs, }) => {
609
762
  const [portalRoot, setPortalRoot] = react.useState(null);
610
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: setPortalRoot, className: "str-video__portal" }), jsxRuntime.jsx(react$1.FloatingOverlay, { children: jsxRuntime.jsx(react$1.FloatingPortal, { root: portalRoot, children: jsxRuntime.jsx("div", { className: "str-video__portal-content", ref: refs.setFloating, children: children }) }) })] }));
763
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: setPortalRoot, className: "str-video__portal" }), jsxRuntime.jsx(react$2.FloatingOverlay, { children: jsxRuntime.jsx(react$2.FloatingPortal, { root: portalRoot, children: jsxRuntime.jsx("div", { className: "str-video__portal-content", ref: refs.setFloating, children: children }) }) })] }));
611
764
  };
612
765
  const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', offset, visualType = exports.MenuVisualType.MENU, children, onToggle, }) => {
613
766
  const [menuShown, setMenuShown] = react.useState(false);
@@ -669,6 +822,7 @@ const GenericMenu = ({ children, onItemClick, }) => {
669
822
  const GenericMenuButtonItem = ({ children, ...rest }) => {
670
823
  return (jsxRuntime.jsx("li", { className: "str-video__generic-menu--item", children: jsxRuntime.jsx("button", { ...rest, children: children }) }));
671
824
  };
825
+ const GenericMenuSeparator = () => (jsxRuntime.jsx("li", { className: "str-video__generic-menu--separator", role: "separator" }));
672
826
 
673
827
  const Icon = ({ className, icon }) => (jsxRuntime.jsx("span", { className: clsx('str-video__icon', icon && `str-video__icon--${icon}`, className) }));
674
828
 
@@ -697,7 +851,7 @@ const ParticipantActionsContextMenu = () => {
697
851
  const [fullscreenModeOn, setFullscreenModeOn] = react.useState(!!document.fullscreenElement);
698
852
  const call = videoReactBindings.useCall();
699
853
  const isPiP = usePictureInPictureState(videoElement ?? undefined);
700
- const { t } = videoReactBindings.useI18n();
854
+ const { t } = useI18n();
701
855
  const { pin, sessionId, userId } = participant;
702
856
  const hasAudioTrack = videoClient.hasAudio(participant);
703
857
  const hasVideoTrack = videoClient.hasVideo(participant);
@@ -769,12 +923,14 @@ const ParticipantActionsContextMenu = () => {
769
923
  return document.exitPictureInPicture().catch(console.error);
770
924
  };
771
925
  const { close } = useMenuContext() || {};
772
- return (jsxRuntime.jsxs(GenericMenu, { onItemClick: close, children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: toggleParticipantPin, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), pin ? t('Unpin') : t('Pin')] }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.PIN_FOR_EVERYONE], children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: pinForEveryone, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Pin for everyone')] }), jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: unpinForEveryone, disabled: !pin || pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('Unpin for everyone')] })] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: blockUser, children: [jsxRuntime.jsx(Icon, { icon: "not-allowed" }), t('Block')] }) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.KICK_USER], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: kickUser, children: [jsxRuntime.jsx(Icon, { icon: "kick-user" }), t('Kick')] }) }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], children: [hasVideoTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteVideo, children: [jsxRuntime.jsx(Icon, { icon: "camera-off-outline" }), t('Turn off video')] })), hasScreenShareTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShare, children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), t('Turn off screen share')] })), hasAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('Mute audio')] })), hasScreenShareAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShareAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('Mute screen share audio')] }))] }), participantViewElement &&
773
- typeof participantViewElement.requestFullscreen !== 'undefined' && (jsxRuntime.jsx(GenericMenuButtonItem, { onClick: toggleFullscreenMode, children: t('{{ direction }} fullscreen', {
774
- direction: fullscreenModeOn ? t('Leave') : t('Enter'),
775
- }) })), videoElement && document.pictureInPictureEnabled && (jsxRuntime.jsx(GenericMenuButtonItem, { onClick: togglePictureInPicture, children: t('{{ direction }} picture-in-picture', {
776
- direction: isPiP ? t('Leave') : t('Enter'),
777
- }) })), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.UPDATE_CALL_PERMISSIONS], children: [jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SEND_AUDIO), children: t('Allow audio') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SEND_VIDEO), children: t('Allow video') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SCREENSHARE), children: t('Allow screen sharing') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SEND_AUDIO), children: t('Disable audio') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SEND_VIDEO), children: t('Disable video') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SCREENSHARE), children: t('Disable screen sharing') })] })] }));
926
+ return (jsxRuntime.jsxs(GenericMenu, { onItemClick: close, children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: toggleParticipantPin, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), pin
927
+ ? t('participantView.unpin.label', 'Unpin')
928
+ : t('participantView.actionsMenu.pin.label', 'Pin')] }), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.PIN_FOR_EVERYONE], children: [jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: pinForEveryone, disabled: pin && !pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('participantView.actionsMenu.pinForEveryone.label', 'Pin for everyone')] }), jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: unpinForEveryone, disabled: !pin || pin.isLocalPin, children: [jsxRuntime.jsx(Icon, { icon: "pin" }), t('participantView.actionsMenu.unpinForEveryone.label', 'Unpin for everyone')] })] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: blockUser, children: [jsxRuntime.jsx(Icon, { icon: "not-allowed" }), t('participantView.actionsMenu.block.label', 'Block')] }) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.KICK_USER], children: jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: kickUser, children: [jsxRuntime.jsx(Icon, { icon: "kick-user" }), t('participantView.actionsMenu.kick.label', 'Kick')] }) }), jsxRuntime.jsx(GenericMenuSeparator, {}), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], children: [hasVideoTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteVideo, children: [jsxRuntime.jsx(Icon, { icon: "camera-off-outline" }), t('participantView.actionsMenu.turnOffVideo.label', 'Turn off video')] })), hasScreenShareTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShare, children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), t('participantView.actionsMenu.turnOffScreenShare.label', 'Turn off screen share')] })), hasAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('participantView.actionsMenu.muteAudio.label', 'Mute audio')] })), hasScreenShareAudioTrack && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: muteScreenShareAudio, children: [jsxRuntime.jsx(Icon, { icon: "no-audio" }), t('participantView.actionsMenu.muteScreenShareAudio.label', 'Mute screen share audio')] }))] }), participantViewElement &&
929
+ typeof participantViewElement.requestFullscreen !== 'undefined' && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: toggleFullscreenMode, children: [jsxRuntime.jsx(Icon, { icon: "fullscreen" }), fullscreenModeOn
930
+ ? t('participantView.actionsMenu.leaveFullscreen.label', 'Leave fullscreen')
931
+ : t('participantView.actionsMenu.enterFullscreen.label', 'Enter fullscreen')] })), videoElement && document.pictureInPictureEnabled && (jsxRuntime.jsxs(GenericMenuButtonItem, { onClick: togglePictureInPicture, children: [jsxRuntime.jsx(Icon, { icon: "pip" }), isPiP
932
+ ? t('participantView.actionsMenu.leavePip.label', 'Leave picture-in-picture')
933
+ : t('participantView.actionsMenu.enterPip.label', 'Enter picture-in-picture')] })), jsxRuntime.jsx(GenericMenuSeparator, {}), jsxRuntime.jsxs(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.UPDATE_CALL_PERMISSIONS], children: [jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SEND_AUDIO), children: t('participantView.actionsMenu.allowAudio.label', 'Allow audio') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SEND_VIDEO), children: t('participantView.actionsMenu.allowVideo.label', 'Allow video') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: grantPermission(videoClient.OwnCapability.SCREENSHARE), children: t('participantView.actionsMenu.allowScreenSharing.label', 'Allow screen sharing') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SEND_AUDIO), children: t('participantView.actionsMenu.disableAudio.label', 'Disable audio') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SEND_VIDEO), children: t('participantView.actionsMenu.disableVideo.label', 'Disable video') }), jsxRuntime.jsx(GenericMenuButtonItem, { onClick: revokePermission(videoClient.OwnCapability.SCREENSHARE), children: t('participantView.actionsMenu.disableScreenSharing.label', 'Disable screen sharing') })] })] }));
778
934
  };
779
935
 
780
936
  const isComponentType = (elementOrComponent) => {
@@ -839,34 +995,39 @@ const BaseVideo = react.forwardRef(function BaseVideoRender({ stream, ...rest },
839
995
  } }));
840
996
  });
841
997
 
842
- const BaseVideoPlaceholder = react.forwardRef(function DefaultVideoPlaceholder({ participant, style, children }, ref) {
998
+ const variantClasses = ({ size = 'md', showOutline }) => [
999
+ `str-video__avatar--size-${size}`,
1000
+ showOutline && 'str-video__avatar--outlined',
1001
+ ];
1002
+ const Avatar = ({ imageSrc, name, style, className, size = 'md', showOutline, ...rest }) => {
843
1003
  const [error, setError] = react.useState(false);
1004
+ if (!imageSrc || error) {
1005
+ return name ? (jsxRuntime.jsx(AvatarFallback, { className: className, style: style, names: name.split(/\s+/).filter(Boolean), size: size, showOutline: showOutline })) : null;
1006
+ }
1007
+ return (jsxRuntime.jsx("img", { onError: () => setError(true), alt: "avatar", className: clsx('str-video__avatar', variantClasses({ size, showOutline }), className), src: imageSrc, style: style, ...rest }));
1008
+ };
1009
+ const AvatarFallback = ({ className, names, style, size = 'md', showOutline, }) => {
1010
+ const isCompact = size === 'xs' || size === 'sm';
1011
+ return (jsxRuntime.jsx("div", { className: clsx('str-video__avatar--initials-fallback', variantClasses({ size, showOutline }), className), style: style, children: jsxRuntime.jsxs("div", { children: [names[0]?.[0], !isCompact && names[1]?.[0]] }) }));
1012
+ };
1013
+
1014
+ const BaseVideoPlaceholder = react.forwardRef(function DefaultVideoPlaceholder({ participant, style, children }, ref) {
844
1015
  const name = participant.name || participant.userId;
845
- return (jsxRuntime.jsxs("div", { className: "str-video__video-placeholder", style: style, ref: ref, children: [(!participant.image || error) &&
846
- (name ? (jsxRuntime.jsx(InitialsFallback, { name: name })) : (jsxRuntime.jsx("div", { className: "str-video__video-placeholder__no-video-label", children: children }))), participant.image && !error && (jsxRuntime.jsx("img", { onError: () => setError(true), alt: name, className: "str-video__video-placeholder__avatar", src: participant.image }))] }));
1016
+ return (jsxRuntime.jsxs("div", { className: "str-video__video-placeholder", style: style, ref: ref, children: [(participant.image || name) && (jsxRuntime.jsx(Avatar, { size: "2xl", showOutline: true, imageSrc: participant.image, name: name })), !participant.image && !name && (jsxRuntime.jsx("div", { className: "str-video__video-placeholder__no-video-label", children: children }))] }));
847
1017
  });
848
- const InitialsFallback = (props) => {
849
- const { name } = props;
850
- const initials = name
851
- .split(' ')
852
- .slice(0, 2)
853
- .map((n) => n[0])
854
- .join('');
855
- return (jsxRuntime.jsx("div", { className: "str-video__video-placeholder__initials-fallback", children: initials }));
856
- };
857
1018
 
858
1019
  const DefaultVideoPlaceholder = react.forwardRef(function DefaultVideoPlaceholderRender(props, ref) {
859
- const { t } = videoReactBindings.useI18n();
860
- return (jsxRuntime.jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is disabled') }));
1020
+ const { t } = useI18n();
1021
+ return (jsxRuntime.jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('common.videoDisabled.text', 'Video is disabled') }));
861
1022
  });
862
1023
 
863
1024
  const DefaultPictureInPicturePlaceholder = react.forwardRef(function DefaultPictureInPicturePlaceholderRender(props, ref) {
864
- const { t } = videoReactBindings.useI18n();
865
- return (jsxRuntime.jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('Video is playing in a popup') }));
1025
+ const { t } = useI18n();
1026
+ return (jsxRuntime.jsx(BaseVideoPlaceholder, { ref: ref, ...props, children: t('videoPlaceholder.videoPlayingInPopup.text', 'Video is playing in a popup') }));
866
1027
  });
867
1028
 
868
1029
  const ABSOLUTE_POSITION_STYLE = { position: 'absolute' };
869
- const Video$1 = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, PictureInPicturePlaceholder = DefaultPictureInPicturePlaceholder, refs, ...rest }) => {
1030
+ const Video = ({ enabled = true, mirror, trackType, participant, className, VideoPlaceholder = DefaultVideoPlaceholder, PictureInPicturePlaceholder = DefaultPictureInPicturePlaceholder, refs, ...rest }) => {
870
1031
  const { sessionId, videoStream, screenShareStream, viewportVisibilityState, isLocalParticipant, userId, } = participant;
871
1032
  const call = videoReactBindings.useCall();
872
1033
  const [videoElement, setVideoElement] = react.useState(null);
@@ -940,7 +1101,7 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
940
1101
  refs?.setVideoElement?.(element);
941
1102
  } })), isPiP && PictureInPicturePlaceholder && (jsxRuntime.jsx(PictureInPicturePlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsxRuntime.jsx(VideoPlaceholder, { style: ABSOLUTE_POSITION_STYLE, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
942
1103
  };
943
- Video$1.displayName = 'Video';
1104
+ Video.displayName = 'Video';
944
1105
 
945
1106
  const useTrackElementVisibility = ({ trackedElement, sessionId, trackType, }) => {
946
1107
  const call = videoReactBindings.useCall();
@@ -951,14 +1112,6 @@ const useTrackElementVisibility = ({ trackedElement, sessionId, trackType, }) =>
951
1112
  }, [trackedElement, call, sessionId, trackType]);
952
1113
  };
953
1114
 
954
- const Avatar = ({ imageSrc, name, style, className, ...rest }) => {
955
- const [error, setError] = react.useState(false);
956
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(!imageSrc || error) && name && (jsxRuntime.jsx(AvatarFallback, { className: className, style: style, names: [name] })), imageSrc && !error && (jsxRuntime.jsx("img", { onError: () => setError(true), alt: "avatar", className: clsx('str-video__avatar', className), src: imageSrc, style: style, ...rest }))] }));
957
- };
958
- const AvatarFallback = ({ className, names, style, }) => {
959
- return (jsxRuntime.jsx("div", { className: clsx('str-video__avatar--initials-fallback', className), style: style, children: jsxRuntime.jsxs("div", { children: [names[0][0], names[1]?.[0]] }) }));
960
- };
961
-
962
1115
  const BackgroundFiltersProviderImpl = react.lazy(() => Promise.resolve().then(function () { return require('./background-filters-DMStbSEg.cjs.js'); }).then((m) => ({
963
1116
  default: m.BackgroundFiltersProvider,
964
1117
  })));
@@ -987,40 +1140,36 @@ const BackgroundFiltersProvider = (props) => {
987
1140
  return (jsxRuntime.jsx(react.Suspense, { fallback: SuspenseFallback, children: jsxRuntime.jsx(BackgroundFiltersProviderImpl, { ...filterProps, ContextProvider: BackgroundFiltersContext }) }));
988
1141
  };
989
1142
 
990
- const IconButton = react.forwardRef(function IconButtonRender(props, ref) {
991
- const { icon, enabled, variant, onClick, className, ...rest } = props;
992
- return (jsxRuntime.jsx("button", { className: clsx('str-video__call-controls__button', className, {
993
- [`str-video__call-controls__button--variant-${variant}`]: variant,
994
- 'str-video__call-controls__button--enabled': enabled,
995
- }), onClick: (e) => {
996
- e.preventDefault();
997
- onClick?.(e);
998
- }, ref: ref, ...rest, children: jsxRuntime.jsx(Icon, { icon: icon }) }));
1143
+ /**
1144
+ * A compact badge for counts, notifications and error markers.
1145
+ *
1146
+ * Without `icon` it is a pill that grows with its content; with `icon` it is a
1147
+ * circle of the size given by `size`.
1148
+ *
1149
+ * The badge carries no positioning of its own — whatever it annotates places
1150
+ * it. For the call controls, rendering a badge among `CompositeButton`'s
1151
+ * children anchors it to the top-right corner of the button.
1152
+ */
1153
+ const Badge = react.forwardRef(function BadgeRender({ variant = 'default', size = 'sm', icon, className, children, ...rest }, ref) {
1154
+ return (jsxRuntime.jsx("span", { ref: ref, className: clsx('str-video__badge', `str-video__badge--${variant}`, `str-video__badge--size-${size}`, icon && 'str-video__badge--icon', className), ...rest, children: icon ? jsxRuntime.jsx(Icon, { icon: icon }) : children }));
999
1155
  });
1000
1156
 
1001
- const CompositeButton = react.forwardRef(function CompositeButtonRender({ disabled, caption, children, className, active, Menu, menuPlacement, menuOffset, title, ToggleMenuButton = DefaultToggleMenuButton, variant, onClick, onMenuToggle, ...restButtonProps }, ref) {
1002
- return (jsxRuntime.jsxs("div", { className: clsx('str-video__composite-button', className, {
1003
- 'str-video__composite-button--caption': caption,
1004
- 'str-video__composite-button--menu': Menu,
1005
- }), title: title, ref: ref, children: [jsxRuntime.jsxs("div", { className: clsx('str-video__composite-button__button-group', {
1006
- 'str-video__composite-button__button-group--active': active,
1007
- 'str-video__composite-button__button-group--active-primary': active && variant === 'primary',
1008
- 'str-video__composite-button__button-group--active-secondary': active && variant === 'secondary',
1009
- 'str-video__composite-button__button-group--disabled': disabled,
1010
- }), children: [jsxRuntime.jsx("button", { type: "button", className: "str-video__composite-button__button", onClick: (e) => {
1011
- e.preventDefault();
1012
- onClick?.(e);
1013
- }, disabled: disabled, ...restButtonProps, children: children }), Menu && (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, onToggle: onMenuToggle, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu }))] }), caption && (jsxRuntime.jsx("div", { className: "str-video__composite-button__caption", children: caption }))] }));
1157
+ const Button = react.forwardRef(function ButtonRender({ variant = 'primary', appearance = 'solid', size = 'md', active, className, type = 'button', children, ...rest }, ref) {
1158
+ return (jsxRuntime.jsx("button", { ref: ref, type: type, className: clsx('str-video__button', `str-video__button--${variant}`, `str-video__button--${appearance}`, `str-video__button--size-${size}`, className), ...rest, "aria-pressed": active ?? rest['aria-pressed'], children: children }));
1014
1159
  });
1015
- const DefaultToggleMenuButton = react.forwardRef(function DefaultToggleMenuButtonRender({ menuShown }, ref) {
1016
- return (jsxRuntime.jsx(IconButton, { className: clsx('str-video__menu-toggle-button', {
1017
- 'str-video__menu-toggle-button--active': menuShown,
1018
- }), icon: menuShown ? 'caret-down' : 'caret-up', ref: ref }));
1160
+
1161
+ const IconButton = react.forwardRef(function IconButtonRender({ icon, className, ...props }, ref) {
1162
+ return (jsxRuntime.jsx(Button, { ref: ref, className: clsx('str-video__button--icon-only', className), ...props, children: jsxRuntime.jsx(Icon, { icon: icon }) }));
1019
1163
  });
1020
1164
 
1021
- const TextButton = ({ children, ...rest }) => {
1022
- return (jsxRuntime.jsx("button", { ...rest, className: "str-video__text-button", children: children }));
1023
- };
1165
+ const CompositeButton = react.forwardRef(function CompositeButtonRender({ appearance, caption, children, className, disabled, Menu, menuOffset, menuPlacement, onMenuToggle, active, size = 'md', title, ToggleMenuButton = CompositeCaret, variant = 'secondary', ...buttonProps }, ref) {
1166
+ return (jsxRuntime.jsxs("div", { ref: ref, title: title, className: clsx('str-video__composite-button', `str-video__composite-button--${variant}`, `str-video__composite-button--size-${size}`, `str-video__composite-button--${appearance ?? 'solid'}`, Menu && 'str-video__composite-button--menu', className), children: [jsxRuntime.jsxs("div", { className: "str-video__composite-button__group", children: [jsxRuntime.jsx(Button, { variant: variant, appearance: appearance, size: size, disabled: disabled, active: active, className: "str-video__composite-button__action str-video__button--icon-only", ...buttonProps, children: children }), Menu &&
1167
+ (disabled ? (jsxRuntime.jsx(CompositeCaret, { menuShown: false, disabled: true })) : (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, onToggle: onMenuToggle, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu })))] }), caption && (jsxRuntime.jsx("span", { className: "str-video__composite-button__caption", children: caption }))] }));
1168
+ });
1169
+ const CompositeCaret = react.forwardRef(function CompositeCaretRender({ menuShown, disabled }, ref) {
1170
+ const { t } = useI18n();
1171
+ return (jsxRuntime.jsx(IconButton, { className: "str-video__composite-button__caret", size: "xs", appearance: "ghost", disabled: disabled, "aria-label": t('common.toggleMenu.ariaLabel', 'Toggle menu'), "aria-haspopup": "menu", "aria-expanded": menuShown, icon: menuShown ? 'caret-down' : 'caret-up', ref: ref }));
1172
+ });
1024
1173
 
1025
1174
  const AcceptCallButton = ({ disabled, onAccept, onClick, }) => {
1026
1175
  const call = videoReactBindings.useCall();
@@ -1039,11 +1188,16 @@ const AcceptCallButton = ({ disabled, onAccept, onClick, }) => {
1039
1188
  }
1040
1189
  }
1041
1190
  }, [onClick, onAccept, call]);
1042
- return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-accept", variant: "success", "data-testid": "accept-call-button", onClick: handleClick }));
1191
+ return (jsxRuntime.jsx(IconButton, { className: "str-video__accept-call-button", size: "sm", disabled: disabled, icon: "call-accept", variant: "secondary", "data-testid": "accept-call-button", onClick: handleClick }));
1043
1192
  };
1044
1193
 
1194
+ const ICON_BY_STATE = {
1195
+ success: 'checkmark',
1196
+ error: 'exclamation-circle-fill',
1197
+ loading: 'loading',
1198
+ };
1045
1199
  const Notification = (props) => {
1046
- const { isVisible, message, children, visibilityTimeout, resetIsVisible, placement = 'top', className, iconClassName = 'str-video__notification__icon', close, } = props;
1200
+ const { isVisible, message, children, visibilityTimeout, resetIsVisible, placement = 'top', className, state, close, } = props;
1047
1201
  const { refs, x, y, strategy } = useFloatingUIPreset({
1048
1202
  placement,
1049
1203
  strategy: 'absolute',
@@ -1056,12 +1210,13 @@ const Notification = (props) => {
1056
1210
  }, visibilityTimeout);
1057
1211
  return () => clearTimeout(timeout);
1058
1212
  }, [isVisible, resetIsVisible, visibilityTimeout]);
1059
- return (jsxRuntime.jsxs("div", { className: "str-video__notification-wrapper", ref: refs.setReference, children: [isVisible && (jsxRuntime.jsxs("div", { className: clsx('str-video__notification', className), ref: refs.setFloating, style: {
1213
+ const icon = state && `str-video__icon str-video__icon--${ICON_BY_STATE[state]}`;
1214
+ return (jsxRuntime.jsxs("div", { className: "str-video__notification-wrapper", ref: refs.setReference, children: [isVisible && (jsxRuntime.jsxs("div", { className: clsx('str-video__notification', state && `str-video__notification--${state}`, close && 'str-video__notification--dismissible', className), ref: refs.setFloating, style: {
1060
1215
  position: strategy,
1061
1216
  top: y ?? 0,
1062
1217
  left: x ?? 0,
1063
1218
  overflowY: 'auto',
1064
- }, children: [iconClassName && jsxRuntime.jsx("i", { className: iconClassName }), jsxRuntime.jsx("span", { className: "str-video__notification__message", children: message }), close ? (jsxRuntime.jsx("i", { className: "str-video__icon str-video__icon--close str-video__notification__close", onClick: close })) : null] })), children] }));
1219
+ }, children: [icon && jsxRuntime.jsx("i", { className: icon }), jsxRuntime.jsx("span", { className: "str-video__notification__message", children: message }), close ? (jsxRuntime.jsx("i", { className: "str-video__icon str-video__icon--close str-video__notification__close", onClick: close })) : null] })), children] }));
1065
1220
  };
1066
1221
 
1067
1222
  const PermissionNotification = (props) => {
@@ -1084,24 +1239,26 @@ const PermissionNotification = (props) => {
1084
1239
  }, [hasPermission]);
1085
1240
  const resetIsVisible = react.useCallback(() => setShowNotification(undefined), []);
1086
1241
  if (isAwaitingApproval) {
1087
- return (jsxRuntime.jsx(Notification, { isVisible: isAwaitingApproval && !hasPermission, message: messageAwaitingApproval, children: children }));
1242
+ return (jsxRuntime.jsx(Notification, { state: "loading", isVisible: isAwaitingApproval && !hasPermission, message: messageAwaitingApproval, children: children }));
1088
1243
  }
1089
- return (jsxRuntime.jsx(Notification, { isVisible: !!showNotification, visibilityTimeout: visibilityTimeout, resetIsVisible: resetIsVisible, message: showNotification === 'granted' ? messageApproved : messageRevoked, children: children }));
1244
+ return (jsxRuntime.jsx(Notification, { state: showNotification === 'granted' ? 'success' : 'error', isVisible: !!showNotification, visibilityTimeout: visibilityTimeout, resetIsVisible: resetIsVisible, message: showNotification === 'granted' ? messageApproved : messageRevoked, children: children }));
1090
1245
  };
1091
1246
 
1092
1247
  const SpeakingWhileMutedNotification = ({ children, text, placement, }) => {
1093
1248
  const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
1094
1249
  const { isSpeakingWhileMuted } = useMicrophoneState();
1095
- const { t } = videoReactBindings.useI18n();
1096
- const message = text ?? t('You are muted. Unmute to speak.');
1250
+ const { t } = useI18n();
1251
+ const message = text ??
1252
+ t('notification.speakingWhileMuted.text', 'You are muted. Unmute to speak.');
1097
1253
  return (jsxRuntime.jsx(Notification, { message: message, isVisible: isSpeakingWhileMuted, placement: placement || 'top-start', children: children }));
1098
1254
  };
1099
1255
 
1100
1256
  const RecordingInProgressNotification = ({ children, text, }) => {
1101
- const { t } = videoReactBindings.useI18n();
1257
+ const { t } = useI18n();
1102
1258
  const { isCallRecordingInProgress } = videoReactBindings.useToggleCallRecording();
1103
1259
  const [isVisible, setVisible] = react.useState(false);
1104
- const message = text ?? t('Recording in progress...');
1260
+ const message = text ??
1261
+ t('notification.recordingInProgress.text', 'Recording in progress...');
1105
1262
  react.useEffect(() => {
1106
1263
  if (isCallRecordingInProgress) {
1107
1264
  setVisible(true);
@@ -1110,12 +1267,12 @@ const RecordingInProgressNotification = ({ children, text, }) => {
1110
1267
  setVisible(false);
1111
1268
  }
1112
1269
  }, [isCallRecordingInProgress]);
1113
- return (jsxRuntime.jsx(Notification, { message: message, iconClassName: "str-video__icon str-video__icon--recording-on", isVisible: isVisible, placement: "top-start", close: () => setVisible(false), children: children }));
1270
+ return (jsxRuntime.jsx(Notification, { message: message, isVisible: isVisible, placement: "top-start", close: () => setVisible(false), children: children }));
1114
1271
  };
1115
1272
 
1116
1273
  const MicCaptureErrorNotification = ({ children, text, placement, }) => {
1117
1274
  const call = videoReactBindings.useCall();
1118
- const { t } = videoReactBindings.useI18n();
1275
+ const { t } = useI18n();
1119
1276
  const [isVisible, setIsVisible] = react.useState(false);
1120
1277
  react.useEffect(() => {
1121
1278
  if (!call)
@@ -1125,8 +1282,8 @@ const MicCaptureErrorNotification = ({ children, text, placement, }) => {
1125
1282
  });
1126
1283
  }, [call]);
1127
1284
  const message = text ??
1128
- t('Your microphone is not capturing audio. Please check your setup.');
1129
- return (jsxRuntime.jsx(Notification, { message: message, isVisible: isVisible, placement: placement, close: () => setIsVisible(false), children: children }));
1285
+ t('notification.micCaptureError.text', 'Your microphone is not capturing audio. Please check your setup.');
1286
+ return (jsxRuntime.jsx(Notification, { message: message, state: "error", isVisible: isVisible, placement: placement, close: () => setIsVisible(false), children: children }));
1130
1287
  };
1131
1288
 
1132
1289
  const LoadingIndicator = ({ className, type = 'spinner', text, tooltip, }) => {
@@ -1134,22 +1291,20 @@ const LoadingIndicator = ({ className, type = 'spinner', text, tooltip, }) => {
1134
1291
  };
1135
1292
 
1136
1293
  const Tooltip = ({ children, referenceElement, tooltipClassName, tooltipPlacement = 'top', visible = false, }) => {
1137
- const arrowRef = react.useRef(null);
1138
- const { refs, x, y, strategy, context } = useFloatingUIPreset({
1294
+ const { refs, x, y, strategy } = useFloatingUIPreset({
1139
1295
  placement: tooltipPlacement,
1140
1296
  strategy: 'absolute',
1141
- middleware: [react$1.arrow({ element: arrowRef })],
1142
1297
  });
1143
1298
  react.useEffect(() => {
1144
1299
  refs.setReference(referenceElement);
1145
1300
  }, [referenceElement, refs]);
1146
1301
  if (!visible)
1147
1302
  return null;
1148
- return (jsxRuntime.jsxs("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
1303
+ return (jsxRuntime.jsx("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
1149
1304
  position: strategy,
1150
1305
  top: y ?? 0,
1151
1306
  left: x ?? 0,
1152
- }, children: [jsxRuntime.jsx(react$1.FloatingArrow, { ref: arrowRef, context: context, fill: "var(--str-video__tooltip--background-color)" }), children] }));
1307
+ }, children: children }));
1153
1308
  };
1154
1309
 
1155
1310
  const useEnterLeaveHandlers = ({ onMouseEnter, onMouseLeave, } = {}) => {
@@ -1201,18 +1356,18 @@ function isNotAllowedError(error) {
1201
1356
  }
1202
1357
 
1203
1358
  const RecordEndConfirmation = (props) => {
1204
- const { t } = videoReactBindings.useI18n();
1359
+ const { t } = useI18n();
1205
1360
  const { toggleCallRecording, isAwaitingResponse } = videoReactBindings.useToggleCallRecording();
1206
1361
  const { close } = useMenuContext();
1207
1362
  const handleClick = createCallControlHandler(props, toggleCallRecording);
1208
- return (jsxRuntime.jsxs("div", { className: "str-video__end-recording__confirmation", children: [jsxRuntime.jsxs("div", { className: "str-video__end-recording__header", children: [jsxRuntime.jsx(Icon, { icon: "recording-on" }), jsxRuntime.jsx("h2", { className: "str-video__end-recording__heading", children: t('End recording') })] }), jsxRuntime.jsx("p", { className: "str-video__end-recording__description", children: t('Are you sure you want end the recording?') }), jsxRuntime.jsxs("div", { className: "str-video__end-recording__actions", children: [jsxRuntime.jsx(CompositeButton, { variant: "secondary", onClick: close, children: t('Cancel') }), jsxRuntime.jsx(CompositeButton, { variant: "primary", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? jsxRuntime.jsx(LoadingIndicator, {}) : t('End recording') })] })] }));
1363
+ return (jsxRuntime.jsxs("div", { className: "str-video__end-recording__confirmation", children: [jsxRuntime.jsxs("div", { className: "str-video__end-recording__header", children: [jsxRuntime.jsx(Icon, { icon: "recording-on" }), jsxRuntime.jsxs("div", { className: "str-video__end-recording__text", children: [jsxRuntime.jsx("h2", { className: "str-video__end-recording__heading", children: t('callControls.recordCallButton.endRecording.title', 'End recording') }), jsxRuntime.jsx("p", { className: "str-video__end-recording__description", children: t('callControls.recordCallButton.confirmEndRecording.description', 'Are you sure you want end the recording?') })] })] }), jsxRuntime.jsxs("div", { className: "str-video__end-recording__actions", children: [jsxRuntime.jsx(Button, { variant: "destructive", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (t('callControls.recordCallButton.endRecording.title', 'End recording')) }), jsxRuntime.jsx(Button, { variant: "secondary", appearance: "outline", onClick: close, children: t('common.cancel.label', 'Cancel') })] })] }));
1209
1364
  };
1210
1365
  const ToggleEndRecordingMenuButton = react.forwardRef(function ToggleEndRecordingMenuButtonRender(props, ref) {
1211
- return (jsxRuntime.jsx(CompositeButton, { ref: ref, active: true, variant: "secondary", "data-testid": "recording-stop-button", children: jsxRuntime.jsx(Icon, { icon: "recording-off" }) }));
1366
+ return (jsxRuntime.jsx(CompositeButton, { ref: ref, active: true, variant: "destructive", "data-testid": "recording-stop-button", children: jsxRuntime.jsx(Icon, { icon: "recording-off" }) }));
1212
1367
  });
1213
1368
  const RecordCallConfirmationButton = (props) => {
1214
1369
  const { caption } = props;
1215
- const { t } = videoReactBindings.useI18n();
1370
+ const { t } = useI18n();
1216
1371
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = videoReactBindings.useToggleCallRecording();
1217
1372
  const handleClick = createCallControlHandler(props, toggleCallRecording);
1218
1373
  if (isCallRecordingInProgress) {
@@ -1222,28 +1377,30 @@ const RecordCallConfirmationButton = (props) => {
1222
1377
  ], children: jsxRuntime.jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: exports.MenuVisualType.PORTAL, children: jsxRuntime.jsx(RecordEndConfirmation, { onError: props.onError }) }) }));
1223
1378
  }
1224
1379
  const title = isAwaitingResponse
1225
- ? t('Waiting for recording to start...')
1226
- : (caption ?? t('Record call'));
1380
+ ? t('callControls.recordCallButton.waitingRecordingStart.title', 'Waiting for recording to start...')
1381
+ : (caption ??
1382
+ t('callControls.recordCallButton.recordCall.title', 'Record call'));
1227
1383
  return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
1228
1384
  videoClient.OwnCapability.START_RECORD_CALL,
1229
1385
  videoClient.OwnCapability.STOP_RECORD_CALL,
1230
- ], children: jsxRuntime.jsx(WithTooltip, { title: title, children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (jsxRuntime.jsx(Icon, { icon: "recording-off" })) }) }) }));
1386
+ ], children: jsxRuntime.jsx(WithTooltip, { title: title, children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: isCallRecordingInProgress ? 'destructive' : 'secondary', "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (jsxRuntime.jsx(Icon, { icon: "recording-off" })) }) }) }));
1231
1387
  };
1232
1388
  const RecordCallButton = (props) => {
1233
1389
  const { caption } = props;
1234
- const { t } = videoReactBindings.useI18n();
1390
+ const { t } = useI18n();
1235
1391
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = videoReactBindings.useToggleCallRecording();
1236
1392
  const handleClick = createCallControlHandler(props, toggleCallRecording);
1237
- let title = caption ?? t('Record call');
1393
+ let title = caption ??
1394
+ t('callControls.recordCallButton.recordCall.title', 'Record call');
1238
1395
  if (isAwaitingResponse) {
1239
1396
  title = isCallRecordingInProgress
1240
- ? t('Waiting for recording to stop...')
1241
- : t('Waiting for recording to start...');
1397
+ ? t('callControls.recordCallButton.waitingRecordingStop.title', 'Waiting for recording to stop...')
1398
+ : t('callControls.recordCallButton.waitingRecordingStart.title', 'Waiting for recording to start...');
1242
1399
  }
1243
1400
  return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
1244
1401
  videoClient.OwnCapability.START_RECORD_CALL,
1245
1402
  videoClient.OwnCapability.STOP_RECORD_CALL,
1246
- ], children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": isCallRecordingInProgress
1403
+ ], children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: isCallRecordingInProgress ? 'destructive' : 'secondary', "data-testid": isCallRecordingInProgress
1247
1404
  ? 'recording-stop-button'
1248
1405
  : 'recording-start-button', title: title, onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (jsxRuntime.jsx(Icon, { icon: isCallRecordingInProgress ? 'recording-on' : 'recording-off' })) }) }));
1249
1406
  };
@@ -1305,8 +1462,8 @@ const ReactionsButton = ({ reactions = defaultReactions, }) => {
1305
1462
  return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.CREATE_REACTION], children: jsxRuntime.jsx(MenuToggle, { placement: "top", ToggleButton: ToggleReactionsMenuButton, visualType: exports.MenuVisualType.MENU, children: jsxRuntime.jsx(DefaultReactionsMenu, { reactions: reactions }) }) }));
1306
1463
  };
1307
1464
  const ToggleReactionsMenuButton = react.forwardRef(function ToggleReactionsMenuButtonRender({ menuShown }, ref) {
1308
- const { t } = videoReactBindings.useI18n();
1309
- return (jsxRuntime.jsx(WithTooltip, { title: t('Reactions'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, variant: "primary", children: jsxRuntime.jsx(Icon, { icon: "reactions" }) }) }));
1465
+ const { t } = useI18n();
1466
+ return (jsxRuntime.jsx(WithTooltip, { title: t('callControls.reactionsButton.reactions.title', 'Reactions'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, children: jsxRuntime.jsx(Icon, { icon: "reactions" }) }) }));
1310
1467
  });
1311
1468
  const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
1312
1469
  const call = videoReactBindings.useCall();
@@ -1321,7 +1478,7 @@ const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
1321
1478
  };
1322
1479
 
1323
1480
  const ScreenShareButton = (props) => {
1324
- const { t } = videoReactBindings.useI18n();
1481
+ const { t } = useI18n();
1325
1482
  const { caption, optimisticUpdates } = props;
1326
1483
  const { useHasOngoingScreenShare, useScreenShareState, useCallSettings } = videoReactBindings.useCallStateHooks();
1327
1484
  const isSomeoneScreenSharing = useHasOngoingScreenShare();
@@ -1343,7 +1500,10 @@ const ScreenShareButton = (props) => {
1343
1500
  await screenShare.toggle();
1344
1501
  }
1345
1502
  });
1346
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SCREENSHARE, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your screen.'), messageAwaitingApproval: t('Awaiting for an approval to share screen.'), messageRevoked: t('You can no longer share your screen.'), children: jsxRuntime.jsx(WithTooltip, { title: caption ?? t('Share screen'), children: jsxRuntime.jsx(CompositeButton, { active: isSomeoneScreenSharing || amIScreenSharing, caption: caption, variant: "primary", "data-testid": isSomeoneScreenSharing
1503
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SCREENSHARE, isAwaitingApproval: isAwaitingPermission, messageApproved: t('callControls.screenShareButton.permissionGranted.text', 'You can now share your screen.'), messageAwaitingApproval: t('callControls.screenShareButton.awaitingApproval.text', 'Awaiting for an approval to share screen.'), messageRevoked: t('callControls.screenShareButton.permissionRevoked.text', 'You can no longer share your screen.'), children: jsxRuntime.jsx(WithTooltip, { title: caption ??
1504
+ t('callControls.screenShareButton.shareScreen.title', 'Share screen'), children: jsxRuntime.jsx(CompositeButton, { active: isSomeoneScreenSharing || amIScreenSharing, caption: caption, variant: isSomeoneScreenSharing || amIScreenSharing
1505
+ ? 'primary'
1506
+ : 'secondary', "data-testid": isSomeoneScreenSharing
1347
1507
  ? 'screen-share-stop-button'
1348
1508
  : 'screen-share-start-button', disabled: disableScreenShareButton, onClick: handleClick, children: jsxRuntime.jsx(Icon, { icon: isSomeoneScreenSharing ? 'screen-share-on' : 'screen-share-off' }) }) }) }) }));
1349
1509
  };
@@ -1399,7 +1559,7 @@ const DeviceLevelIndicator = ({ deviceId }) => {
1399
1559
  const DeviceAudioPreviewItem = ({ device, onSelect, }) => {
1400
1560
  if (device.deviceId === 'default')
1401
1561
  return null;
1402
- return (jsxRuntime.jsxs("label", { className: `str-video__device-settings__option${device.isSelected ? ' str-video__device-settings__option--selected' : ''}`, htmlFor: `audioinput--${device.deviceId}`, children: [jsxRuntime.jsx("input", { type: "radio", name: "audioinput", value: device.deviceId, id: `audioinput--${device.deviceId}`, checked: device.isSelected, onChange: (e) => onSelect(e.target.value) }), device.label, jsxRuntime.jsx(DeviceLevelIndicator, { deviceId: device.deviceId })] }));
1562
+ return (jsxRuntime.jsxs("label", { className: `str-video__device-settings__option${device.isSelected ? ' str-video__device-settings__option--selected' : ''}`, htmlFor: `audioinput--${device.deviceId}`, children: [jsxRuntime.jsx("input", { type: "radio", name: "audioinput", value: device.deviceId, id: `audioinput--${device.deviceId}`, checked: device.isSelected, onChange: (e) => onSelect(e.target.value) }), jsxRuntime.jsx("span", { className: "str-video__device-settings__option__label", children: device.label }), jsxRuntime.jsx(DeviceLevelIndicator, { deviceId: device.deviceId })] }));
1403
1563
  };
1404
1564
 
1405
1565
  const SelectContext = react.createContext({});
@@ -1409,12 +1569,12 @@ const Select = (props) => {
1409
1569
  const [activeIndex, setActiveIndex] = react.useState(null);
1410
1570
  const [selectedIndex, setSelectedIndex] = react.useState(defaultSelectedIndex);
1411
1571
  const [selectedLabel, setSelectedLabel] = react.useState(defaultSelectedLabel);
1412
- const { refs, context } = react$1.useFloating({
1572
+ const { refs, context } = react$2.useFloating({
1413
1573
  placement: 'bottom-start',
1414
1574
  open: isOpen,
1415
1575
  onOpenChange: setIsOpen,
1416
- whileElementsMounted: react$1.autoUpdate,
1417
- middleware: [react$1.flip()],
1576
+ whileElementsMounted: react$2.autoUpdate,
1577
+ middleware: [react$2.flip()],
1418
1578
  });
1419
1579
  const elementsRef = react.useRef([]);
1420
1580
  const labelsRef = react.useRef([]);
@@ -1434,34 +1594,34 @@ const Select = (props) => {
1434
1594
  handleSelect(index);
1435
1595
  }
1436
1596
  };
1437
- const listNav = react$1.useListNavigation(context, {
1597
+ const listNav = react$2.useListNavigation(context, {
1438
1598
  listRef: elementsRef,
1439
1599
  activeIndex,
1440
1600
  selectedIndex,
1441
1601
  onNavigate: setActiveIndex,
1442
1602
  });
1443
- const typeahead = react$1.useTypeahead(context, {
1603
+ const typeahead = react$2.useTypeahead(context, {
1444
1604
  listRef: labelsRef,
1445
1605
  activeIndex,
1446
1606
  selectedIndex,
1447
1607
  onMatch: handleTypeaheadMatch,
1448
1608
  });
1449
- const click = react$1.useClick(context);
1450
- const dismiss = react$1.useDismiss(context);
1451
- const role = react$1.useRole(context, { role: 'listbox' });
1452
- const { getReferenceProps, getFloatingProps, getItemProps } = react$1.useInteractions([listNav, typeahead, click, dismiss, role]);
1609
+ const click = react$2.useClick(context);
1610
+ const dismiss = react$2.useDismiss(context);
1611
+ const role = react$2.useRole(context, { role: 'listbox' });
1612
+ const { getReferenceProps, getFloatingProps, getItemProps } = react$2.useInteractions([listNav, typeahead, click, dismiss, role]);
1453
1613
  const selectContext = react.useMemo(() => ({
1454
1614
  activeIndex,
1455
1615
  selectedIndex,
1456
1616
  getItemProps,
1457
1617
  handleSelect,
1458
1618
  }), [activeIndex, selectedIndex, getItemProps, handleSelect]);
1459
- return (jsxRuntime.jsxs("div", { className: "str-video__dropdown", children: [jsxRuntime.jsxs("div", { className: "str-video__dropdown-selected", ref: refs.setReference, tabIndex: 0, ...getReferenceProps(), children: [jsxRuntime.jsxs("label", { className: "str-video__dropdown-selected__label", children: [icon && (jsxRuntime.jsx(Icon, { className: "str-video__dropdown-selected__icon", icon: icon })), selectedLabel] }), jsxRuntime.jsx(Icon, { className: "str-video__dropdown-selected__chevron", icon: isOpen ? 'chevron-up' : 'chevron-down' })] }), jsxRuntime.jsx(SelectContext.Provider, { value: selectContext, children: isOpen && (jsxRuntime.jsx(react$1.FloatingFocusManager, { context: context, modal: false, children: jsxRuntime.jsx("div", { className: "str-video__dropdown-list", ref: refs.setFloating, ...getFloatingProps(), children: jsxRuntime.jsx(react$1.FloatingList, { elementsRef: elementsRef, labelsRef: labelsRef, children: children }) }) })) })] }));
1619
+ return (jsxRuntime.jsxs("div", { className: "str-video__dropdown", children: [jsxRuntime.jsxs("div", { className: "str-video__dropdown-selected", ref: refs.setReference, tabIndex: 0, ...getReferenceProps(), children: [jsxRuntime.jsxs("label", { className: "str-video__dropdown-selected__label", children: [icon && (jsxRuntime.jsx(Icon, { className: "str-video__dropdown-selected__icon", icon: icon })), jsxRuntime.jsx("span", { className: "str-video__dropdown-selected__text", children: selectedLabel })] }), jsxRuntime.jsx(Icon, { className: "str-video__dropdown-selected__chevron", icon: isOpen ? 'chevron-up' : 'chevron-down' })] }), jsxRuntime.jsx(SelectContext.Provider, { value: selectContext, children: isOpen && (jsxRuntime.jsx(react$2.FloatingFocusManager, { context: context, modal: false, children: jsxRuntime.jsx("div", { className: "str-video__dropdown-list", ref: refs.setFloating, ...getFloatingProps(), children: jsxRuntime.jsx(react$2.FloatingList, { elementsRef: elementsRef, labelsRef: labelsRef, children: children }) }) })) })] }));
1460
1620
  };
1461
1621
  const DropDownSelectOption = (props) => {
1462
1622
  const { selected, label, icon } = props;
1463
1623
  const { getItemProps, handleSelect } = react.useContext(SelectContext);
1464
- const { ref, index } = react$1.useListItem();
1624
+ const { ref, index } = react$2.useListItem();
1465
1625
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__dropdown-option', {
1466
1626
  'str-video__dropdown-option--selected': selected,
1467
1627
  }), ref: ref, ...getItemProps({
@@ -1477,7 +1637,7 @@ const DeviceSelectorOption = ({ disabled, id, label, onChange, name, selected, d
1477
1637
  return (jsxRuntime.jsxs("label", { className: clsx('str-video__device-settings__option', {
1478
1638
  'str-video__device-settings__option--selected': selected,
1479
1639
  'str-video__device-settings__option--disabled': disabled,
1480
- }), htmlFor: id, children: [jsxRuntime.jsx("input", { type: "radio", name: name, onChange: onChange, value: value, id: id, checked: selected, defaultChecked: defaultChecked, disabled: disabled }), label] }));
1640
+ }), htmlFor: id, children: [jsxRuntime.jsx("input", { type: "radio", name: name, onChange: onChange, value: value, id: id, checked: selected, defaultChecked: defaultChecked, disabled: disabled }), jsxRuntime.jsx("span", { className: "str-video__device-settings__option__label", children: label })] }));
1481
1641
  };
1482
1642
  const DeviceSelectorList = (props) => {
1483
1643
  const { devices = [], selectedDeviceId, title, type, onChange, children, } = props;
@@ -1537,8 +1697,8 @@ const SpeakerTest = (props) => {
1537
1697
  const { selectedDevice } = useSpeakerState();
1538
1698
  const audioElementRef = react.useRef(null);
1539
1699
  const [isPlaying, setIsPlaying] = react.useState(false);
1540
- const { t } = videoReactBindings.useI18n();
1541
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"2.0.0-beta.0"}/assets/piano.mp3`, } = props;
1700
+ const { t } = useI18n();
1701
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"2.0.0-beta.1"}/assets/piano.mp3`, } = props;
1542
1702
  // Update audio output device when selection changes
1543
1703
  react.useEffect(() => {
1544
1704
  const audio = audioElementRef.current;
@@ -1573,7 +1733,9 @@ const SpeakerTest = (props) => {
1573
1733
  }
1574
1734
  }, [isPlaying, audioUrl]);
1575
1735
  const handleAudioEnded = react.useCallback(() => setIsPlaying(false), []);
1576
- return (jsxRuntime.jsxs("div", { className: "str-video__speaker-test", children: [jsxRuntime.jsx("audio", { ref: audioElementRef, onEnded: handleAudioEnded, onPause: handleAudioEnded }), jsxRuntime.jsx(CompositeButton, { className: "str-video__speaker-test__button", onClick: handleStartTest, type: "button", children: jsxRuntime.jsxs("div", { className: "str-video__speaker-test__button-content", children: [jsxRuntime.jsx(Icon, { icon: "speaker" }), isPlaying ? t('Stop test') : t('Test speaker')] }) })] }));
1736
+ return (jsxRuntime.jsxs("div", { className: "str-video__speaker-test", children: [jsxRuntime.jsx("audio", { ref: audioElementRef, onEnded: handleAudioEnded, onPause: handleAudioEnded }), jsxRuntime.jsx(CompositeButton, { className: "str-video__speaker-test__button", appearance: "outline", size: "sm", onClick: handleStartTest, type: "button", children: jsxRuntime.jsxs("div", { className: "str-video__speaker-test__button-content", children: [jsxRuntime.jsx(Icon, { icon: "speaker" }), isPlaying
1737
+ ? t('deviceSettings.speakerTest.stopTest.label', 'Stop test')
1738
+ : t('deviceSettings.speakerTest.testSpeaker.label', 'Test speaker')] }) })] }));
1577
1739
  };
1578
1740
 
1579
1741
  const DeviceSelectorAudioInput = ({ title, visualType, volumeIndicatorVisible = true, }) => {
@@ -1581,7 +1743,7 @@ const DeviceSelectorAudioInput = ({ title, visualType, volumeIndicatorVisible =
1581
1743
  const { microphone, selectedDevice, devices } = useMicrophoneState();
1582
1744
  return (jsxRuntime.jsx(DeviceSelector, { devices: devices || [], selectedDeviceId: selectedDevice, type: "audioinput", onChange: async (deviceId) => {
1583
1745
  await microphone.select(deviceId);
1584
- }, title: title, visualType: visualType, icon: "mic", PreviewItem: DeviceAudioPreviewItem, children: volumeIndicatorVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" }), jsxRuntime.jsx(AudioVolumeIndicator, {})] })) }));
1746
+ }, title: title, visualType: visualType, icon: "mic", PreviewItem: DeviceAudioPreviewItem, children: volumeIndicatorVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(AudioVolumeIndicator, {}), jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" })] })) }));
1585
1747
  };
1586
1748
  const DeviceSelectorAudioOutput = ({ title, visualType, speakerTestVisible = true, speakerTestAudioUrl, }) => {
1587
1749
  const { useSpeakerState } = videoReactBindings.useCallStateHooks();
@@ -1590,7 +1752,7 @@ const DeviceSelectorAudioOutput = ({ title, visualType, speakerTestVisible = tru
1590
1752
  return null;
1591
1753
  return (jsxRuntime.jsx(DeviceSelector, { devices: devices, type: "audiooutput", selectedDeviceId: selectedDevice, onChange: (deviceId) => {
1592
1754
  speaker.select(deviceId);
1593
- }, title: title, visualType: visualType, icon: "speaker", children: speakerTestVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" }), jsxRuntime.jsx(SpeakerTest, { audioUrl: speakerTestAudioUrl })] })) }));
1755
+ }, title: title, visualType: visualType, icon: "speaker", children: speakerTestVisible && jsxRuntime.jsx(SpeakerTest, { audioUrl: speakerTestAudioUrl }) }));
1594
1756
  };
1595
1757
 
1596
1758
  const DeviceVideoPreview = ({ deviceId }) => {
@@ -1641,38 +1803,41 @@ const DeviceSettings = ({ visualType = exports.MenuVisualType.MENU, }) => {
1641
1803
  return (jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleDeviceSettingsMenuButton, visualType: visualType, children: jsxRuntime.jsx(Menu, {}) }));
1642
1804
  };
1643
1805
  const Menu = () => {
1644
- const { t } = videoReactBindings.useI18n();
1645
- return (jsxRuntime.jsxs("div", { className: "str-video__device-settings", children: [jsxRuntime.jsx(DeviceSelectorVideo, { title: t('Select a Camera') }), jsxRuntime.jsx(DeviceSelectorAudioInput, { title: t('Select a Mic') }), jsxRuntime.jsx(DeviceSelectorAudioOutput, { title: t('Select Speakers') })] }));
1806
+ const { t } = useI18n();
1807
+ return (jsxRuntime.jsxs("div", { className: "str-video__device-settings", children: [jsxRuntime.jsx(DeviceSelectorVideo, { title: t('deviceSettings.selectCamera.title', 'Select a Camera') }), jsxRuntime.jsx(DeviceSelectorAudioInput, { title: t('deviceSettings.selectMic.title', 'Select a Mic') }), jsxRuntime.jsx(DeviceSelectorAudioOutput, { title: t('deviceSettings.selectSpeakers.title', 'Select Speakers') })] }));
1646
1808
  };
1647
1809
  const ToggleDeviceSettingsMenuButton = react.forwardRef(function ToggleDeviceSettingsMenuButtonRender({ menuShown }, ref) {
1648
- const { t } = videoReactBindings.useI18n();
1649
- return (jsxRuntime.jsx(IconButton, { className: clsx('str-video__device-settings__button', {
1810
+ const { t } = useI18n();
1811
+ return (jsxRuntime.jsx(IconButton, { active: menuShown, size: "sm", variant: "secondary", className: clsx('str-video__device-settings__button', {
1650
1812
  'str-video__device-settings__button--active': menuShown,
1651
- }), title: t('Toggle device menu'), icon: "device-settings", ref: ref }));
1813
+ }), title: t('deviceSettings.toggleDeviceMenu.title', 'Toggle device menu'), icon: "device-settings", ref: ref }));
1652
1814
  });
1653
1815
 
1654
1816
  const ToggleAudioPreviewButton = (props) => {
1655
1817
  const { caption, Menu = DeviceSelectorAudioInput, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1656
- const { t } = videoReactBindings.useI18n();
1818
+ const { t } = useI18n();
1657
1819
  const { useMicrophoneState, useLocalParticipant } = videoReactBindings.useCallStateHooks();
1658
1820
  const { microphone, hasBrowserPermission, isPromptingPermission, optionsAwareIsMute, isTogglePending, } = useMicrophoneState({ optimisticUpdates });
1659
1821
  const localParticipant = useLocalParticipant();
1660
1822
  const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(videoClient.SfuModels.TrackType.AUDIO);
1661
1823
  const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1662
1824
  const handleClick = createCallControlHandler(props, () => microphone.toggle());
1663
- return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1664
- ? t('Check your browser audio permissions')
1665
- : isSystemMuted
1666
- ? t('Microphone is paused by your system')
1667
- : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute
1825
+ return (jsxRuntime.jsx(WithTooltip, { title: isPromptingPermission
1826
+ ? t('common.waitingForPermission.title', 'Waiting for permission')
1827
+ : !hasBrowserPermission
1828
+ ? t('callControls.toggleAudioButton.checkBrowserAudioPermissions.title', 'Check your browser audio permissions')
1829
+ : isSystemMuted
1830
+ ? t('callControls.toggleAudioButton.microphonePausedBySystem.title', 'Microphone is paused by your system')
1831
+ : (caption ??
1832
+ t('callControls.toggleAudioButton.mic.title', 'Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: optionsAwareIsMute ? 'destructive' : 'secondary', disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute
1668
1833
  ? 'preview-audio-unmute-button'
1669
1834
  : 'preview-audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1670
1835
  setTooltipDisabled(shown);
1671
1836
  onMenuToggle?.(shown);
1672
- }, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Microphone is paused by your system'), children: "!" }))] }) }));
1837
+ }, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'mic' : 'mic-off' }), isPromptingPermission ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "question-mark-fill" })) : !hasBrowserPermission || isSystemMuted ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "exclamation-mark-fill" })) : null] }) }));
1673
1838
  };
1674
1839
  const ToggleAudioPublishingButton = (props) => {
1675
- const { t } = videoReactBindings.useI18n();
1840
+ const { t } = useI18n();
1676
1841
  const { caption, Menu = jsxRuntime.jsx(DeviceSelectorAudioInput, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1677
1842
  const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.RequestPermissionRequestPermissionsEnum.SEND_AUDIO);
1678
1843
  const { useMicrophoneState, useLocalParticipant } = videoReactBindings.useCallStateHooks();
@@ -1688,43 +1853,49 @@ const ToggleAudioPublishingButton = (props) => {
1688
1853
  await microphone.toggle();
1689
1854
  }
1690
1855
  });
1691
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_AUDIO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now speak.'), messageAwaitingApproval: t('Awaiting for an approval to speak.'), messageRevoked: t('You can no longer speak.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1692
- ? t('You have no permission to share your audio')
1693
- : !hasBrowserPermission
1694
- ? t('Check your browser mic permissions')
1695
- : isSystemMuted
1696
- ? t('Microphone is paused by your system')
1697
- : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1856
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_AUDIO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('callControls.toggleAudioButton.permissionGranted.text', 'You can now speak.'), messageAwaitingApproval: t('callControls.toggleAudioButton.awaitingApproval.text', 'Awaiting for an approval to speak.'), messageRevoked: t('callControls.toggleAudioButton.permissionRevoked.text', 'You can no longer speak.'), children: jsxRuntime.jsx(WithTooltip, { title: isPromptingPermission
1857
+ ? t('common.waitingForPermission.title', 'Waiting for permission')
1858
+ : !hasPermission
1859
+ ? t('callControls.toggleAudioButton.noPermissionToShareAudio.title', 'You have no permission to share your audio')
1860
+ : !hasBrowserPermission
1861
+ ? t('callControls.toggleAudioButton.checkBrowserMicPermissions.title', 'Check your browser mic permissions')
1862
+ : isSystemMuted
1863
+ ? t('callControls.toggleAudioButton.microphonePausedBySystem.title', 'Microphone is paused by your system')
1864
+ : (caption ??
1865
+ t('callControls.toggleAudioButton.mic.title', 'Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: optionsAwareIsMute ? 'destructive' : 'secondary', disabled: !hasBrowserPermission ||
1698
1866
  !hasPermission ||
1699
1867
  // disable button while the toggle action is pending when not using optimistic updates
1700
1868
  (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1701
1869
  setTooltipDisabled(shown);
1702
1870
  onMenuToggle?.(shown);
1703
- }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && hasPermission && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Microphone is paused by your system'), children: "!" }))] }) }) }) }));
1871
+ }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'mic-off' : 'mic' }), isPromptingPermission ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "question-mark-fill" })) : !hasBrowserPermission || !hasPermission || isSystemMuted ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "exclamation-mark-fill" })) : null] }) }) }) }));
1704
1872
  };
1705
1873
 
1706
1874
  const ToggleVideoPreviewButton = (props) => {
1707
1875
  const { caption, Menu = DeviceSelectorVideo, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1708
- const { t } = videoReactBindings.useI18n();
1876
+ const { t } = useI18n();
1709
1877
  const { useCameraState, useLocalParticipant } = videoReactBindings.useCallStateHooks();
1710
1878
  const { camera, hasBrowserPermission, isPromptingPermission, isTogglePending, optionsAwareIsMute, } = useCameraState({ optimisticUpdates });
1711
1879
  const localParticipant = useLocalParticipant();
1712
1880
  const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(videoClient.SfuModels.TrackType.VIDEO);
1713
1881
  const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1714
1882
  const handleClick = createCallControlHandler(props, () => camera.toggle());
1715
- return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1716
- ? t('Check your browser video permissions')
1717
- : isSystemMuted
1718
- ? t('Camera is paused by your system')
1719
- : (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optionsAwareIsMute
1883
+ return (jsxRuntime.jsx(WithTooltip, { title: isPromptingPermission
1884
+ ? t('common.waitingForPermission.title', 'Waiting for permission')
1885
+ : !hasBrowserPermission
1886
+ ? t('callControls.toggleVideoButton.checkBrowserVideoPermissions.title', 'Check your browser video permissions')
1887
+ : isSystemMuted
1888
+ ? t('callControls.toggleVideoButton.cameraPausedBySystem.title', 'Camera is paused by your system')
1889
+ : (caption ??
1890
+ t('callControls.toggleVideoButton.video.title', 'Video')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: optionsAwareIsMute ? 'destructive' : 'secondary', "data-testid": optionsAwareIsMute
1720
1891
  ? 'preview-video-unmute-button'
1721
1892
  : 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1722
1893
  setTooltipDisabled(shown);
1723
1894
  onMenuToggle?.(shown);
1724
- }, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }));
1895
+ }, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'camera' : 'camera-off' }), isPromptingPermission ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "question-mark-fill" })) : !hasBrowserPermission || isSystemMuted ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "exclamation-mark-fill" })) : null] }) }));
1725
1896
  };
1726
1897
  const ToggleVideoPublishingButton = (props) => {
1727
- const { t } = videoReactBindings.useI18n();
1898
+ const { t } = useI18n();
1728
1899
  const { caption, Menu = jsxRuntime.jsx(DeviceSelectorVideo, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1729
1900
  const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.RequestPermissionRequestPermissionsEnum.SEND_VIDEO);
1730
1901
  const { useCameraState, useCallSettings, useLocalParticipant } = videoReactBindings.useCallStateHooks();
@@ -1742,36 +1913,37 @@ const ToggleVideoPublishingButton = (props) => {
1742
1913
  await camera.toggle();
1743
1914
  }
1744
1915
  });
1745
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1746
- ? t('You have no permission to share your video')
1747
- : !hasBrowserPermission
1748
- ? t('Check your browser video permissions')
1749
- : !isPublishingVideoAllowed
1750
- ? t('Video publishing is disabled by the system')
1751
- : isSystemMuted
1752
- ? t('Camera is paused by your system')
1753
- : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1916
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('callControls.toggleVideoButton.permissionGranted.text', 'You can now share your video.'), messageAwaitingApproval: t('callControls.toggleVideoButton.awaitingApproval.text', 'Awaiting for an approval to share your video.'), messageRevoked: t('callControls.toggleVideoButton.permissionRevoked.text', 'You can no longer share your video.'), children: jsxRuntime.jsx(WithTooltip, { title: isPromptingPermission
1917
+ ? t('common.waitingForPermission.title', 'Waiting for permission')
1918
+ : !hasPermission
1919
+ ? t('callControls.toggleVideoButton.noPermissionToShareVideo.title', 'You have no permission to share your video')
1920
+ : !hasBrowserPermission
1921
+ ? t('callControls.toggleVideoButton.checkBrowserVideoPermissions.title', 'Check your browser video permissions')
1922
+ : !isPublishingVideoAllowed
1923
+ ? t('callControls.toggleVideoButton.videoPublishingDisabled.title', 'Video publishing is disabled by the system')
1924
+ : isSystemMuted
1925
+ ? t('callControls.toggleVideoButton.cameraPausedBySystem.title', 'Camera is paused by your system')
1926
+ : caption ||
1927
+ t('callControls.toggleVideoButton.video.title', 'Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: optionsAwareIsMute ? 'destructive' : 'secondary', disabled: !hasBrowserPermission ||
1754
1928
  !hasPermission ||
1755
1929
  !isPublishingVideoAllowed ||
1756
1930
  (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1757
1931
  setTooltipDisabled(shown);
1758
1932
  onMenuToggle?.(shown);
1759
- }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1933
+ }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), isPromptingPermission ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "question-mark-fill" })) : !hasBrowserPermission ||
1760
1934
  !hasPermission ||
1761
- !isPublishingVideoAllowed) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted &&
1762
- hasBrowserPermission &&
1763
- hasPermission &&
1764
- isPublishingVideoAllowed && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }) }) }));
1935
+ !isPublishingVideoAllowed ||
1936
+ isSystemMuted ? (jsxRuntime.jsx(Badge, { variant: "error", icon: "exclamation-mark-fill" })) : null] }) }) }) }));
1765
1937
  };
1766
1938
 
1767
1939
  const EndCallMenu = (props) => {
1768
1940
  const { onLeave, onEnd } = props;
1769
- const { t } = videoReactBindings.useI18n();
1770
- return (jsxRuntime.jsxs("div", { className: "str-video__end-call__confirmation", children: [jsxRuntime.jsxs("button", { className: "str-video__button str-video__end-call__leave", type: "button", "data-testid": "leave-call-button", onClick: onLeave, children: [jsxRuntime.jsx(Icon, { className: "str-video__button__icon str-video__end-call__leave-icon", icon: "logout" }), t('Leave call')] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.END_CALL], children: jsxRuntime.jsxs("button", { className: "str-video__button str-video__end-call__end", type: "button", "data-testid": "end-call-for-all-button", onClick: onEnd, children: [jsxRuntime.jsx(Icon, { className: "str-video__button__icon str-video__end-call__end-icon", icon: "call-end" }), t('End call for all')] }) })] }));
1941
+ const { t } = useI18n();
1942
+ return (jsxRuntime.jsxs("div", { className: "str-video__end-call__confirmation", children: [jsxRuntime.jsxs(Button, { variant: "secondary", appearance: "outline", className: "str-video__end-call__leave", "data-testid": "leave-call-button", onClick: onLeave, children: [jsxRuntime.jsx(Icon, { icon: "logout" }), t('callControls.cancelCallButton.leaveCall.title', 'Leave call')] }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.END_CALL], children: jsxRuntime.jsxs(Button, { variant: "destructive", className: "str-video__end-call__end", "data-testid": "end-call-for-all-button", onClick: onEnd, children: [jsxRuntime.jsx(Icon, { icon: "call-end" }), t('callControls.cancelCallButton.endCallForAll.label', 'End call for all')] }) })] }));
1771
1943
  };
1772
1944
  const CancelCallToggleMenuButton = react.forwardRef(function CancelCallToggleMenuButtonRender({ menuShown }, ref) {
1773
- const { t } = videoReactBindings.useI18n();
1774
- return (jsxRuntime.jsx(WithTooltip, { title: t('Leave call'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(IconButton, { icon: menuShown ? 'close' : 'call-end', variant: menuShown ? 'active' : 'danger', "data-testid": "leave-call-button", ref: ref }) }));
1945
+ const { t } = useI18n();
1946
+ return (jsxRuntime.jsx(WithTooltip, { title: t('callControls.cancelCallButton.leaveCall.title', 'Leave call'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(IconButton, { icon: menuShown ? 'close' : 'call-end', variant: "destructive", "data-testid": "leave-call-button", ref: ref }) }));
1775
1947
  });
1776
1948
  const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
1777
1949
  const call = videoReactBindings.useCall();
@@ -1807,9 +1979,9 @@ const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
1807
1979
  }, [onClick, onLeave, call]);
1808
1980
  return (jsxRuntime.jsx(MenuToggle, { placement: "top-start", ToggleButton: CancelCallToggleMenuButton, children: jsxRuntime.jsx(EndCallMenu, { onEnd: handleEndCall, onLeave: handleLeave }) }));
1809
1981
  };
1810
- const CancelCallButton = ({ disabled, caption, onClick, onLeave, }) => {
1982
+ const CancelCallButton = ({ disabled, caption, size, onClick, onLeave, }) => {
1811
1983
  const call = videoReactBindings.useCall();
1812
- const { t } = videoReactBindings.useI18n();
1984
+ const { t } = useI18n();
1813
1985
  const handleClick = react.useCallback(async (e) => {
1814
1986
  if (onClick) {
1815
1987
  onClick(e);
@@ -1825,7 +1997,8 @@ const CancelCallButton = ({ disabled, caption, onClick, onLeave, }) => {
1825
1997
  }
1826
1998
  }
1827
1999
  }, [onClick, onLeave, call]);
1828
- return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: caption ?? t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
2000
+ return (jsxRuntime.jsx(IconButton, { size: size, disabled: disabled, icon: "call-end", variant: "destructive", title: caption ??
2001
+ t('callControls.cancelCallButton.leaveCall.title', 'Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
1829
2002
  };
1830
2003
 
1831
2004
  const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-video__call-controls", children: [jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(SpeakingWhileMutedNotification, { children: jsxRuntime.jsx(ToggleAudioPublishingButton, {}) }) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(ToggleVideoPublishingButton, {}) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.CREATE_REACTION], children: jsxRuntime.jsx(ReactionsButton, {}) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(ScreenShareButton, {}) }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
@@ -1833,188 +2006,12 @@ const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-
1833
2006
  videoClient.OwnCapability.STOP_RECORD_CALL,
1834
2007
  ], children: jsxRuntime.jsx(RecordCallButton, {}) }), jsxRuntime.jsx(CancelCallButton, { onLeave: onLeave })] }));
1835
2008
 
1836
- const CallStatsLatencyChart = react.lazy(() => Promise.resolve().then(function () { return require('./latency-chart-CpL1M_s0.cjs.js'); }));
1837
- var Status;
1838
- (function (Status) {
1839
- Status["GOOD"] = "Good";
1840
- Status["OK"] = "Ok";
1841
- Status["BAD"] = "Bad";
1842
- })(Status || (Status = {}));
1843
- const CallStats = (props) => {
1844
- const { latencyLowBound = 75, latencyHighBound = 400, audioJitterLowBound = 10, audioJitterHighBound = 30, videoJitterLowBound = 20, videoJitterHighBound = 50, showCodecInfo = false, LatencyChartSuspenseFallback = null, } = props;
1845
- const [latencyBuffer, setLatencyBuffer] = react.useState(() => {
1846
- const now = Date.now();
1847
- return Array.from({ length: 20 }, (_, i) => ({ x: now + i, y: 0 }));
1848
- });
1849
- const { t } = videoReactBindings.useI18n();
1850
- const [publishBitrate, setPublishBitrate] = react.useState('-');
1851
- const [subscribeBitrate, setSubscribeBitrate] = react.useState('-');
1852
- const [publishAudioBitrate, setPublishAudioBitrate] = react.useState('-');
1853
- const [subscribeAudioBitrate, setSubscribeAudioBitrate] = react.useState('-');
1854
- const previousStats = react.useRef(undefined);
1855
- const { useCallStatsReport } = videoReactBindings.useCallStateHooks();
1856
- const callStatsReport = useCallStatsReport();
1857
- react.useEffect(() => {
1858
- if (!callStatsReport)
1859
- return;
1860
- if (!previousStats.current) {
1861
- previousStats.current = callStatsReport;
1862
- return;
1863
- }
1864
- const previousCallStatsReport = previousStats.current;
1865
- setPublishBitrate(() => {
1866
- return calculatePublishBitrate(previousCallStatsReport, callStatsReport);
1867
- });
1868
- setSubscribeBitrate(() => {
1869
- return calculateSubscribeBitrate(previousCallStatsReport, callStatsReport);
1870
- });
1871
- setPublishAudioBitrate(() => {
1872
- return calculatePublishAudioBitrate(previousCallStatsReport, callStatsReport);
1873
- });
1874
- setSubscribeAudioBitrate(() => {
1875
- return calculateSubscribeAudioBitrate(previousCallStatsReport, callStatsReport);
1876
- });
1877
- setLatencyBuffer((latencyBuf) => {
1878
- const newLatencyBuffer = latencyBuf.slice(-19);
1879
- newLatencyBuffer.push({
1880
- x: callStatsReport.timestamp,
1881
- y: callStatsReport.publisherStats.averageRoundTripTimeInMs ||
1882
- callStatsReport.publisherAudioStats.averageRoundTripTimeInMs,
1883
- });
1884
- return newLatencyBuffer;
1885
- });
1886
- previousStats.current = callStatsReport;
1887
- }, [callStatsReport]);
1888
- const latencyComparison = {
1889
- lowBound: latencyLowBound,
1890
- highBound: latencyHighBound,
1891
- value: callStatsReport?.publisherStats.averageRoundTripTimeInMs || 0,
1892
- };
1893
- const audioJitterComparison = {
1894
- lowBound: audioJitterLowBound,
1895
- highBound: audioJitterHighBound,
1896
- };
1897
- const videoJitterComparison = {
1898
- lowBound: videoJitterLowBound,
1899
- highBound: videoJitterHighBound,
1900
- };
1901
- return (jsxRuntime.jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.') })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__latencychart", children: jsxRuntime.jsx(react.Suspense, { fallback: LatencyChartSuspenseFallback, children: jsxRuntime.jsx(CallStatsLatencyChart, { values: latencyBuffer }) }) }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Video performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Region'), value: callStatsReport.datacenter }), jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Receive jitter'), value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
1902
- ...videoJitterComparison,
1903
- value: callStatsReport.subscriberStats.averageJitterInMs,
1904
- } }), jsxRuntime.jsx(StatCard, { label: t('Publish jitter'), value: `${callStatsReport.publisherStats.averageJitterInMs} ms.`, comparison: {
1905
- ...videoJitterComparison,
1906
- value: callStatsReport.publisherStats.averageJitterInMs,
1907
- } }), jsxRuntime.jsx(StatCard, { label: `${t('Publish resolution')}${showCodecInfo ? formatCodec(callStatsReport) : ''}`, value: toFrameSize(callStatsReport.publisherStats) }), jsxRuntime.jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsxRuntime.jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsxRuntime.jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "mic" }), t('Audio Performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key audio data points below to assess audio performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherAudioStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Audio codec'), value: formatAudioCodec(callStatsReport) }), jsxRuntime.jsx(StatCard, { label: t('Audio bitrate (publish)'), value: publishAudioBitrate }), jsxRuntime.jsx(StatCard, { label: t('Audio bitrate (receive)'), value: subscribeAudioBitrate }), jsxRuntime.jsx(StatCard, { label: t('Audio jitter (publish)'), value: `${callStatsReport.publisherAudioStats.averageJitterInMs} ms.`, comparison: {
1908
- ...audioJitterComparison,
1909
- value: callStatsReport.publisherAudioStats.averageJitterInMs,
1910
- } }), jsxRuntime.jsx(StatCard, { label: t('Audio jitter (receive)'), value: `${callStatsReport.subscriberAudioStats.averageJitterInMs} ms.`, comparison: {
1911
- ...audioJitterComparison,
1912
- value: callStatsReport.subscriberAudioStats.averageJitterInMs,
1913
- } })] })] })) }));
1914
- };
1915
- const StatCardExplanation = (props) => {
1916
- const { description } = props;
1917
- const [isOpen, setIsOpen] = react.useState(false);
1918
- const { refs, floatingStyles, context } = react$1.useFloating({
1919
- open: isOpen,
1920
- onOpenChange: setIsOpen,
1921
- });
1922
- const hover = react$1.useHover(context);
1923
- const { getReferenceProps, getFloatingProps } = react$1.useInteractions([hover]);
1924
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "str-video__call-explanation", ref: refs.setReference, ...getReferenceProps(), children: jsxRuntime.jsx(Icon, { className: "str-video__call-explanation__icon", icon: "info" }) }), isOpen && (jsxRuntime.jsx("div", { className: "str-video__call-explanation__description", ref: refs.setFloating, style: floatingStyles, ...getFloatingProps(), children: description }))] }));
1925
- };
1926
- const StatsTag = (props) => {
1927
- const { children, status } = props;
1928
- return (jsxRuntime.jsx("div", { className: clsx('str-video__call-stats__tag', {
1929
- 'str-video__call-stats__tag--good': status === Status.GOOD,
1930
- 'str-video__call-stats__tag--ok': status === Status.OK,
1931
- 'str-video__call-stats__tag--bad': status === Status.BAD,
1932
- }), children: jsxRuntime.jsx("div", { className: "str-video__call-stats__tag__text", children: children }) }));
1933
- };
1934
- const StatCard = (props) => {
1935
- const { label, value, description, comparison } = props;
1936
- const { t } = videoReactBindings.useI18n();
1937
- const status = comparison ? toStatus(comparison) : undefined;
1938
- return (jsxRuntime.jsxs("div", { className: "str-video__call-stats__card", children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-content", children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-label", children: [label, description && jsxRuntime.jsx(StatCardExplanation, { description: description })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__card-value", children: value })] }), status && jsxRuntime.jsx(StatsTag, { status: status, children: t(status) })] }));
1939
- };
1940
- const toStatus = (config) => {
1941
- const { value, lowBound, highBound } = config;
1942
- if (value <= lowBound)
1943
- return Status.GOOD;
1944
- if (value >= lowBound && value <= highBound)
1945
- return Status.OK;
1946
- if (value >= highBound)
1947
- return Status.BAD;
1948
- return Status.GOOD;
1949
- };
1950
- const toFrameSize = (stats) => {
1951
- const { highestFrameWidth: w, highestFrameHeight: h, highestFramesPerSecond: fps, } = stats;
1952
- let size = `-`;
1953
- if (w && h) {
1954
- size = `${w}x${h}`;
1955
- if (fps) {
1956
- size += `@${fps}fps.`;
1957
- }
1958
- }
1959
- return size;
1960
- };
1961
- const formatCodec = (callStatsReport) => {
1962
- const { codecPerTrackType } = callStatsReport.publisherStats;
1963
- if (!codecPerTrackType || !codecPerTrackType[videoClient.SfuModels.TrackType.VIDEO]) {
1964
- return '';
1965
- }
1966
- const [, name] = codecPerTrackType[videoClient.SfuModels.TrackType.VIDEO].split('/');
1967
- return name ? ` (${name})` : '';
1968
- };
1969
- const formatAudioCodec = (callStatsReport) => {
1970
- const { codecPerTrackType } = callStatsReport.publisherAudioStats;
1971
- if (!codecPerTrackType || !codecPerTrackType[videoClient.SfuModels.TrackType.AUDIO]) {
1972
- return '';
1973
- }
1974
- const [, name] = codecPerTrackType[videoClient.SfuModels.TrackType.AUDIO].split('/');
1975
- return name ?? '';
1976
- };
1977
- const calculatePublishBitrate = (previousCallStatsReport, callStatsReport) => {
1978
- const { publisherStats: { totalBytesSent: previousTotalBytesSent, timestamp: previousTimestamp, }, } = previousCallStatsReport;
1979
- const { publisherStats: { totalBytesSent, timestamp }, } = callStatsReport;
1980
- const bytesSent = totalBytesSent - previousTotalBytesSent;
1981
- const timeElapsed = timestamp - previousTimestamp;
1982
- return `${((bytesSent * 8) / timeElapsed).toFixed(2)} kbps`;
1983
- };
1984
- const calculateSubscribeBitrate = (previousCallStatsReport, callStatsReport) => {
1985
- const { subscriberStats: { totalBytesReceived: previousTotalBytesReceived, timestamp: previousTimestamp, }, } = previousCallStatsReport;
1986
- const { subscriberStats: { totalBytesReceived, timestamp }, } = callStatsReport;
1987
- const bytesReceived = totalBytesReceived - previousTotalBytesReceived;
1988
- const timeElapsed = timestamp - previousTimestamp;
1989
- return `${((bytesReceived * 8) / timeElapsed).toFixed(2)} kbps`;
1990
- };
1991
- const calculatePublishAudioBitrate = (previousCallStatsReport, callStatsReport) => {
1992
- const previousAudioStats = previousCallStatsReport.publisherAudioStats;
1993
- const audioStats = callStatsReport.publisherAudioStats;
1994
- const bytesSent = audioStats.totalBytesSent - previousAudioStats.totalBytesSent;
1995
- const timeElapsed = audioStats.timestamp - previousAudioStats.timestamp;
1996
- return `${((bytesSent * 8) / timeElapsed).toFixed(2)} kbps`;
1997
- };
1998
- const calculateSubscribeAudioBitrate = (previousCallStatsReport, callStatsReport) => {
1999
- const previousAudioStats = previousCallStatsReport.subscriberAudioStats;
2000
- const audioStats = callStatsReport.subscriberAudioStats;
2001
- const bytesReceived = audioStats.totalBytesReceived - previousAudioStats.totalBytesReceived;
2002
- const timeElapsed = audioStats.timestamp - previousAudioStats.timestamp;
2003
- return `${((bytesReceived * 8) / timeElapsed).toFixed(2)} kbps`;
2004
- };
2005
-
2006
- const CallStatsButton = () => (jsxRuntime.jsx(MenuToggle, { placement: "top-end", ToggleButton: ToggleMenuButton, children: jsxRuntime.jsx(CallStats, {}) }));
2007
- const ToggleMenuButton = react.forwardRef(function ToggleMenuButtonRender(props, ref) {
2008
- const { t } = videoReactBindings.useI18n();
2009
- const { caption, menuShown } = props;
2010
- return (jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, caption: caption, title: caption || t('Statistics'), "data-testid": "stats-button", children: jsxRuntime.jsx(Icon, { icon: "stats" }) }));
2011
- });
2012
-
2013
2009
  const ToggleAudioOutputButton = (props) => {
2014
- const { t } = videoReactBindings.useI18n();
2010
+ const { t } = useI18n();
2015
2011
  const { caption, Menu = DeviceSelectorAudioOutput, menuPlacement = 'top', onMenuToggle, } = props;
2016
2012
  const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
2017
- return (jsxRuntime.jsx(WithTooltip, { title: caption || t('Speakers'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsx(CompositeButton, { Menu: Menu, menuPlacement: menuPlacement, caption: caption, "data-testid": "audio-output-button", onMenuToggle: (shown) => {
2013
+ return (jsxRuntime.jsx(WithTooltip, { title: caption ||
2014
+ t('callControls.toggleAudioOutputButton.speakers.title', 'Speakers'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsx(CompositeButton, { Menu: Menu, menuPlacement: menuPlacement, caption: caption, "data-testid": "audio-output-button", onMenuToggle: (shown) => {
2018
2015
  setTooltipDisabled(shown);
2019
2016
  onMenuToggle?.(shown);
2020
2017
  }, children: jsxRuntime.jsx(Icon, { icon: "speaker" }) }) }));
@@ -2031,34 +2028,43 @@ const BlockedUserListingItem = ({ userId }) => {
2031
2028
  if (userId)
2032
2029
  call?.unblockUser(userId);
2033
2030
  };
2034
- return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx("div", { className: "str-video__participant-listing-item__display-name", children: userId }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsx(TextButton, { onClick: unblockUserClickHandler, children: "Unblock" }) })] }));
2031
+ return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx("div", { className: "str-video__participant-listing-item__display-name", children: userId }), jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.BLOCK_USERS], children: jsxRuntime.jsx(Button, { appearance: "ghost", size: "xs", variant: "secondary", onClick: unblockUserClickHandler, children: "Unblock" }) })] }));
2035
2032
  };
2036
2033
 
2037
2034
  const CallParticipantListHeader = ({ onClose, }) => {
2038
2035
  const { useParticipants, useAnonymousParticipantCount } = videoReactBindings.useCallStateHooks();
2039
2036
  const participants = useParticipants();
2040
2037
  const anonymousParticipantCount = useAnonymousParticipantCount();
2041
- const { t } = videoReactBindings.useI18n();
2042
- return (jsxRuntime.jsxs("div", { className: "str-video__participant-list-header", children: [jsxRuntime.jsxs("div", { className: "str-video__participant-list-header__title", children: [t('Participants'), ' ', jsxRuntime.jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsxRuntime.jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('Anonymous', { count: anonymousParticipantCount }) }))] }), jsxRuntime.jsx(IconButton, { onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
2038
+ const { t } = useI18n();
2039
+ return (jsxRuntime.jsxs("div", { className: "str-video__participant-list-header", children: [jsxRuntime.jsxs("div", { className: "str-video__participant-list-header__title", children: [t('common.participants.label', 'Participants'), ' ', jsxRuntime.jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsxRuntime.jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('participantList.anonymousCount.text', ', and ({{ anonymousCount }}) anonymous', { anonymousCount: anonymousParticipantCount }) }))] }), jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
2043
2040
  };
2044
2041
 
2045
2042
  const CallParticipantListingItem = react.memo(function CallParticipantListingItemRender({ participant, DisplayName = DefaultDisplayName, }) {
2046
2043
  const isAudioOn = videoClient.hasAudio(participant);
2047
2044
  const isVideoOn = videoClient.hasVideo(participant);
2048
2045
  const isPinnedOn = videoClient.isPinned(participant);
2049
- const { t } = videoReactBindings.useI18n();
2050
- return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsxRuntime.jsx(DisplayName, { participant: participant }), jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsxRuntime.jsx(MediaIndicator, { title: isAudioOn ? t('Microphone on') : t('Microphone off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isAudioOn ? 'mic' : 'mic-off'}`) }), jsxRuntime.jsx(MediaIndicator, { title: isVideoOn ? t('Camera on') : t('Camera off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isVideoOn ? 'camera' : 'camera-off'}`) }), isPinnedOn && (jsxRuntime.jsx(MediaIndicator, { title: t('Pinned'), className: clsx('str-video__participant-listing-item__icon', 'str-video__participant-listing-item__icon-pinned') })), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$2, children: jsxRuntime.jsx(ParticipantViewContext.Provider, { value: { participant, trackType: 'none' }, children: jsxRuntime.jsx(ParticipantActionsContextMenu, {}) }) })] })] }));
2046
+ const { t } = useI18n();
2047
+ return (jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item", children: [jsxRuntime.jsx(Avatar, { name: participant.name, imageSrc: participant.image }), jsxRuntime.jsx(DisplayName, { participant: participant }), jsxRuntime.jsxs("div", { className: "str-video__participant-listing-item__media-indicator-group", children: [jsxRuntime.jsx(MediaIndicator, { title: isAudioOn
2048
+ ? t('participantList.microphoneOn.title', 'Microphone on')
2049
+ : t('participantList.microphoneOff.title', 'Microphone off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isAudioOn ? 'mic' : 'mic-off'}`) }), jsxRuntime.jsx(MediaIndicator, { title: isVideoOn
2050
+ ? t('participantList.cameraOn.title', 'Camera on')
2051
+ : t('participantList.cameraOff.title', 'Camera off'), className: clsx('str-video__participant-listing-item__icon', `str-video__participant-listing-item__icon-${isVideoOn ? 'camera' : 'camera-off'}`) }), isPinnedOn && (jsxRuntime.jsx(MediaIndicator, { title: t('participantList.pinned.title', 'Pinned'), className: clsx('str-video__participant-listing-item__icon', 'str-video__participant-listing-item__icon-pinned') })), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$2, children: jsxRuntime.jsx(ParticipantViewContext.Provider, { value: { participant, trackType: 'none' }, children: jsxRuntime.jsx(ParticipantActionsContextMenu, {}) }) })] })] }));
2051
2052
  });
2052
2053
  CallParticipantListingItem.displayName = 'CallParticipantListingItem';
2053
2054
  const MediaIndicator = (props) => (jsxRuntime.jsx(WithTooltip, { ...props }));
2054
2055
  const DefaultDisplayName = ({ participant }) => {
2055
2056
  const connectedUser = videoReactBindings.useConnectedUser();
2056
- const { t } = videoReactBindings.useI18n();
2057
- const meFlag = participant.userId === connectedUser?.id ? t('Me') : '';
2058
- const nameOrId = participant.name || participant.userId || t('Unknown');
2057
+ const { t } = useI18n();
2058
+ const meFlag = participant.userId === connectedUser?.id
2059
+ ? t('participantList.me.text', 'Me')
2060
+ : '';
2061
+ const nameOrId = participant.name ||
2062
+ participant.userId ||
2063
+ t('participantList.unknown.text', 'Unknown');
2059
2064
  let displayName;
2060
2065
  if (!participant.name) {
2061
- displayName = meFlag || nameOrId || t('Unknown');
2066
+ displayName =
2067
+ meFlag || nameOrId || t('participantList.unknown.text', 'Unknown');
2062
2068
  }
2063
2069
  else if (meFlag) {
2064
2070
  displayName = `${nameOrId} (${meFlag})`;
@@ -2069,14 +2075,14 @@ const DefaultDisplayName = ({ participant }) => {
2069
2075
  return (jsxRuntime.jsx(WithTooltip, { className: "str-video__participant-listing-item__display-name", title: displayName, children: displayName }));
2070
2076
  };
2071
2077
  const ToggleButton$2 = react.forwardRef(function ToggleButtonRender(props, ref) {
2072
- return jsxRuntime.jsx(IconButton, { enabled: props.menuShown, icon: "ellipsis", ref: ref });
2078
+ return (jsxRuntime.jsx(IconButton, { className: "str-video__participant-listing-item__menu-button", active: props.menuShown, size: "sm", variant: "secondary", ref: ref, icon: "ellipsis" }));
2073
2079
  });
2074
2080
 
2075
2081
  const CallParticipantListing = ({ data, }) => (jsxRuntime.jsx("div", { className: "str-video__participant-listing", children: data.map((participant) => (jsxRuntime.jsx(CallParticipantListingItem, { participant: participant }, participant.sessionId))) }));
2076
2082
 
2077
2083
  const EmptyParticipantSearchList = () => {
2078
- const { t } = videoReactBindings.useI18n();
2079
- return (jsxRuntime.jsx("div", { className: "str-video__participant-list--empty", children: t('No participants found') }));
2084
+ const { t } = useI18n();
2085
+ return (jsxRuntime.jsx("div", { className: "str-video__participant-list--empty", children: t('participantList.noParticipantsFound.text', 'No participants found') }));
2080
2086
  };
2081
2087
 
2082
2088
  const SearchInput = ({ exitSearch, isActive, ...rest }) => {
@@ -2096,7 +2102,7 @@ const SearchInput = ({ exitSearch, isActive, ...rest }) => {
2096
2102
  }, [inputElement]);
2097
2103
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__search-input__container', {
2098
2104
  'str-video__search-input__container--active': isActive,
2099
- }), children: [jsxRuntime.jsx("input", { placeholder: "Search", ...rest, ref: setInputElement }), isActive ? (jsxRuntime.jsx("button", { className: "str-video__search-input__clear-btn", onClick: exitSearch, children: jsxRuntime.jsx("span", { className: "str-video__search-input__icon--active" }) })) : (jsxRuntime.jsx("span", { className: "str-video__search-input__icon" }))] }));
2105
+ }), children: [jsxRuntime.jsx("span", { className: "str-video__search-input__icon" }), jsxRuntime.jsx("input", { placeholder: "Search", ...rest, ref: setInputElement }), isActive && (jsxRuntime.jsx("button", { className: "str-video__search-input__clear-btn", onClick: exitSearch, children: jsxRuntime.jsx("span", { className: "str-video__search-input__icon--active" }) }))] }));
2100
2106
  };
2101
2107
 
2102
2108
  function SearchResults({ EmptySearchResultComponent, LoadingIndicator: LoadingIndicator$1 = LoadingIndicator, searchQueryInProgress, searchResults, SearchResultList, }) {
@@ -2155,11 +2161,11 @@ const CallParticipantsList = ({ onClose, activeUsersSearchFn, blockedUsersSearch
2155
2161
  };
2156
2162
  const CallParticipantListContentHeader = ({ userListType, setUserListType, }) => {
2157
2163
  const call = videoReactBindings.useCall();
2158
- const { t } = videoReactBindings.useI18n();
2164
+ const { t } = useI18n();
2159
2165
  const muteAll = react.useCallback(() => {
2160
2166
  call?.muteAllUsers('audio');
2161
2167
  }, [call]);
2162
- return (jsxRuntime.jsxs("div", { className: "str-video__participant-list__content-header", children: [jsxRuntime.jsx("div", { className: "str-video__participant-list__content-header-title", children: userListType === 'active' && (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], hasPermissionsOnly: true, children: jsxRuntime.jsx(TextButton, { onClick: muteAll, children: t('Mute all') }) })) }), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$1, children: jsxRuntime.jsx(GenericMenu, { children: Object.keys(UserListTypes).map((lt) => (jsxRuntime.jsx(GenericMenuButtonItem, { "aria-selected": lt === userListType, onClick: () => setUserListType(lt), children: UserListTypes[lt] }, lt))) }) })] }));
2168
+ return (jsxRuntime.jsxs("div", { className: "str-video__participant-list__content-header", children: [jsxRuntime.jsx("div", { className: "str-video__participant-list__content-header-title", children: userListType === 'active' && (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.MUTE_USERS], hasPermissionsOnly: true, children: jsxRuntime.jsx(Button, { appearance: "ghost", size: "xs", variant: "secondary", onClick: muteAll, children: t('participantList.muteAll.label', 'Mute all') }) })) }), jsxRuntime.jsx(MenuToggle, { placement: "bottom-end", ToggleButton: ToggleButton$1, children: jsxRuntime.jsx(GenericMenu, { children: Object.keys(UserListTypes).map((lt) => (jsxRuntime.jsx(GenericMenuButtonItem, { "aria-current": lt === userListType, onClick: () => setUserListType(lt), children: UserListTypes[lt] }, lt))) }) })] }));
2163
2169
  };
2164
2170
  const ActiveUsersSearchResults = ({ searchQuery, activeUsersSearchFn: activeUsersSearchFnFromProps, debounceSearchInterval, }) => {
2165
2171
  const { useParticipants } = videoReactBindings.useCallStateHooks();
@@ -2200,7 +2206,7 @@ const BlockedUsersSearchResults = ({ blockedUsersSearchFn: blockedUsersSearchFnF
2200
2206
  return (jsxRuntime.jsx(SearchResults, { EmptySearchResultComponent: EmptyParticipantSearchList, LoadingIndicator: LoadingIndicator, searchQueryInProgress: searchQueryInProgress, searchResults: searchQuery ? searchResults : blockedUsers, SearchResultList: BlockedUserListing }));
2201
2207
  };
2202
2208
  const ToggleButton$1 = react.forwardRef(function ToggleButtonRender(props, ref) {
2203
- return jsxRuntime.jsx(IconButton, { enabled: props.menuShown, icon: "filter", ref: ref });
2209
+ return (jsxRuntime.jsx(IconButton, { className: "str-video__participant-list__filter-button", active: props.menuShown, size: "sm", variant: "secondary", ref: ref, icon: "filter" }));
2204
2210
  });
2205
2211
 
2206
2212
  const CallPreview = (props) => {
@@ -2221,8 +2227,8 @@ const CallPreview = (props) => {
2221
2227
  };
2222
2228
 
2223
2229
  const CallRecordingListHeader = ({ callRecordings, onRefresh, }) => {
2224
- const { t } = videoReactBindings.useI18n();
2225
- return (jsxRuntime.jsxs("div", { className: "str-video__call-recording-list__header", children: [jsxRuntime.jsxs("div", { className: "str-video__call-recording-list__title", children: [jsxRuntime.jsx("span", { children: t('Call Recordings') }), callRecordings.length ? jsxRuntime.jsxs("span", { children: ["(", callRecordings.length, ")"] }) : null] }), onRefresh && (jsxRuntime.jsx(IconButton, { icon: "refresh", title: t('Refresh'), onClick: onRefresh }))] }));
2230
+ const { t } = useI18n();
2231
+ return (jsxRuntime.jsxs("div", { className: "str-video__call-recording-list__header", children: [jsxRuntime.jsxs("div", { className: "str-video__call-recording-list__title", children: [jsxRuntime.jsx("span", { children: t('callRecordingList.callRecordings.title', 'Call Recordings') }), callRecordings.length ? jsxRuntime.jsxs("span", { children: ["(", callRecordings.length, ")"] }) : null] }), onRefresh && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", title: t('callRecordingList.refresh.title', 'Refresh'), onClick: onRefresh, icon: "refresh" }))] }));
2226
2232
  };
2227
2233
 
2228
2234
  const dateFormat = (date) => {
@@ -2341,7 +2347,7 @@ const RingingCallControls = () => {
2341
2347
  if (!call)
2342
2348
  return null;
2343
2349
  const buttonsDisabled = callCallingState !== videoClient.CallingState.RINGING;
2344
- return (jsxRuntime.jsx("div", { className: "str-video__pending-call-controls", children: call.isCreatedByMe ? (jsxRuntime.jsx(CancelCallButton, { disabled: buttonsDisabled })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(AcceptCallButton, { disabled: buttonsDisabled }), jsxRuntime.jsx(CancelCallButton, { onClick: () => {
2350
+ return (jsxRuntime.jsx("div", { className: "str-video__pending-call-controls", children: call.isCreatedByMe ? (jsxRuntime.jsx(CancelCallButton, { size: "sm", disabled: buttonsDisabled })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(AcceptCallButton, { disabled: buttonsDisabled }), jsxRuntime.jsx(CancelCallButton, { size: "sm", onClick: () => {
2345
2351
  const reason = call.isCreatedByMe ? 'cancel' : 'decline';
2346
2352
  call.leave({ reject: true, reason }).catch((err) => {
2347
2353
  console.error(`Failed to reject`, err);
@@ -2349,22 +2355,42 @@ const RingingCallControls = () => {
2349
2355
  }, disabled: buttonsDisabled })] })) }));
2350
2356
  };
2351
2357
 
2352
- const CALLING_STATE_TO_LABEL = {
2353
- [videoClient.CallingState.JOINING]: 'Joining',
2354
- [videoClient.CallingState.RINGING]: 'Ringing',
2355
- [videoClient.CallingState.MIGRATING]: 'Migrating',
2356
- [videoClient.CallingState.RECONNECTING]: 'Re-connecting',
2357
- [videoClient.CallingState.RECONNECTING_FAILED]: 'Failed',
2358
- [videoClient.CallingState.OFFLINE]: 'No internet connection',
2359
- [videoClient.CallingState.IDLE]: '',
2360
- [videoClient.CallingState.UNKNOWN]: '',
2361
- [videoClient.CallingState.JOINED]: 'Joined',
2362
- [videoClient.CallingState.LEFT]: 'Left call',
2358
+ /**
2359
+ * The label rendered for the current calling state.
2360
+ *
2361
+ * A `switch` of literal `t()` calls rather than a `Record<CallingState, string>` fed into a `t()`
2362
+ * lookup on a runtime value: the literal keys are statically extractable, which is what makes
2363
+ * `Migrating`, `Failed`, `Joined` and `Left call` translatable - none had a catalog entry before.
2364
+ * `IDLE` and `UNKNOWN` render nothing and get no key.
2365
+ */
2366
+ const getCallingStateLabel = (callingState, t) => {
2367
+ switch (callingState) {
2368
+ case videoClient.CallingState.JOINING:
2369
+ return t('ringingCall.callingState.joining.text', 'Joining');
2370
+ case videoClient.CallingState.RINGING:
2371
+ return t('ringingCall.callingState.ringing.text', 'Ringing');
2372
+ case videoClient.CallingState.MIGRATING:
2373
+ return t('ringingCall.callingState.migrating.text', 'Migrating');
2374
+ case videoClient.CallingState.RECONNECTING:
2375
+ return t('ringingCall.callingState.reconnecting.text', 'Re-connecting');
2376
+ case videoClient.CallingState.RECONNECTING_FAILED:
2377
+ return t('ringingCall.callingState.reconnectingFailed.text', 'Failed');
2378
+ case videoClient.CallingState.OFFLINE:
2379
+ return t('ringingCall.callingState.offline.text', 'No internet connection');
2380
+ case videoClient.CallingState.JOINED:
2381
+ return t('ringingCall.callingState.joined.text', 'Joined');
2382
+ case videoClient.CallingState.LEFT:
2383
+ return t('ringingCall.callingState.left.text', 'Left call');
2384
+ case videoClient.CallingState.IDLE:
2385
+ case videoClient.CallingState.UNKNOWN:
2386
+ default:
2387
+ return undefined;
2388
+ }
2363
2389
  };
2364
2390
  const RingingCall = (props) => {
2365
2391
  const { includeSelf = false, totalMembersToShow = 3 } = props;
2366
2392
  const call = videoReactBindings.useCall();
2367
- const { t } = videoReactBindings.useI18n();
2393
+ const { t } = useI18n();
2368
2394
  const { useCallCallingState, useCallMembers } = videoReactBindings.useCallStateHooks();
2369
2395
  const callingState = useCallCallingState();
2370
2396
  const members = useCallMembers();
@@ -2385,8 +2411,8 @@ const RingingCall = (props) => {
2385
2411
  membersToShow.splice(0, 1, self.user);
2386
2412
  }
2387
2413
  }
2388
- const callingStateLabel = CALLING_STATE_TO_LABEL[callingState];
2389
- return (jsxRuntime.jsxs("div", { className: "str-video__call-panel str-video__call-panel--ringing", children: [jsxRuntime.jsx("div", { className: "str-video__call-panel__members-list", children: membersToShow.map((user) => (jsxRuntime.jsxs("div", { className: "str-video__call-panel__member-box", children: [jsxRuntime.jsx(Avatar, { name: user.name, imageSrc: user.image }), user.name && (jsxRuntime.jsx("div", { className: "str-video__member_details", children: jsxRuntime.jsx("span", { className: "str-video__member_name", children: user.name }) }))] }, user.id))) }), callingStateLabel && (jsxRuntime.jsx("div", { className: "str-video__call-panel__calling-state-label", children: t(callingStateLabel) })), [videoClient.CallingState.RINGING, videoClient.CallingState.JOINING].includes(callingState) && (jsxRuntime.jsx(RingingCallControls, {}))] }));
2414
+ const callingStateLabel = getCallingStateLabel(callingState, t);
2415
+ return (jsxRuntime.jsxs("div", { className: "str-video__call-panel str-video__call-panel--ringing", children: [jsxRuntime.jsx("div", { className: "str-video__call-panel__members-list", children: membersToShow.map((user) => (jsxRuntime.jsxs("div", { className: "str-video__call-panel__member-box", children: [jsxRuntime.jsx(Avatar, { name: user.name, imageSrc: user.image }), user.name && (jsxRuntime.jsx("div", { className: "str-video__member_details", children: jsxRuntime.jsx("span", { className: "str-video__member_name", children: user.name }) }))] }, user.id))) }), callingStateLabel && (jsxRuntime.jsx("div", { className: "str-video__call-panel__calling-state-label", children: callingStateLabel })), [videoClient.CallingState.RINGING, videoClient.CallingState.JOINING].includes(callingState) && (jsxRuntime.jsx(RingingCallControls, {}))] }));
2390
2416
  };
2391
2417
 
2392
2418
  const byNameOrId = (a, b) => {
@@ -2438,7 +2464,7 @@ const PermissionRequests = () => {
2438
2464
  // don't render anything if there are no permission requests
2439
2465
  if (permissionRequests.length === 0)
2440
2466
  return null;
2441
- return (jsxRuntime.jsxs("div", { className: "str-video__permission-requests", ref: refs.setReference, children: [jsxRuntime.jsxs("div", { className: "str-video__permission-requests__notification", children: [jsxRuntime.jsxs("span", { className: "str-video__permission-requests__notification__message", children: [permissionRequests.length, " pending permission requests"] }), jsxRuntime.jsx(Button, { type: "button", onClick: () => {
2467
+ return (jsxRuntime.jsxs("div", { className: "str-video__permission-requests", ref: refs.setReference, children: [jsxRuntime.jsxs("div", { className: "str-video__permission-requests__notification", children: [jsxRuntime.jsxs("span", { className: "str-video__permission-requests__notification__message", children: [permissionRequests.length, " pending permission requests"] }), jsxRuntime.jsx(Button, { variant: "secondary", appearance: "outline", size: "sm", className: "str-video__permission-request__button", onClick: () => {
2442
2468
  setExpanded((e) => !e);
2443
2469
  }, children: expanded ? 'Hide requests' : 'Show requests' })] }), expanded && (jsxRuntime.jsx(PermissionRequestList, { ref: refs.setFloating, style: {
2444
2470
  position: strategy,
@@ -2449,51 +2475,71 @@ const PermissionRequests = () => {
2449
2475
  };
2450
2476
  const PermissionRequestList = react.forwardRef(function PermissionRequestListRender(props, ref) {
2451
2477
  const { permissionRequests, handleUpdatePermission, ...rest } = props;
2452
- const { t } = videoReactBindings.useI18n();
2478
+ const { t } = useI18n();
2453
2479
  return (jsxRuntime.jsx("div", { className: "str-video__permission-requests-list", ref: ref, ...rest, children: permissionRequests.map((request, reqIndex) => {
2454
2480
  const { user, permissions } = request;
2455
- return (jsxRuntime.jsx(react.Fragment, { children: permissions.map((permission) => (jsxRuntime.jsxs("div", { className: "str-video__permission-request", children: [jsxRuntime.jsx("div", { className: "str-video__permission-request__message", children: messageForPermission(user.name || user.id, permission, t) }), jsxRuntime.jsx(Button, { className: "str-video__permission-request__button--allow", type: "button", onClick: handleUpdatePermission(request, 'grant'), children: t('Allow') }), jsxRuntime.jsx(Button, { className: "str-video__permission-request__button--reject", type: "button", onClick: handleUpdatePermission(request, 'revoke'), children: t('Revoke') }), jsxRuntime.jsx(Button, { className: "str-video__permission-request__button--reject", type: "button", onClick: handleUpdatePermission(request, 'dismiss'), children: t('Dismiss') })] }, permission))) }, `${user.id}/${reqIndex}`));
2481
+ return (jsxRuntime.jsx(react.Fragment, { children: permissions.map((permission) => (jsxRuntime.jsxs("div", { className: "str-video__permission-request", children: [jsxRuntime.jsx("div", { className: "str-video__permission-request__message", children: messageForPermission(user.name || user.id, permission, t) }), jsxRuntime.jsx(Button, { variant: "primary", appearance: "solid", size: "sm", className: "str-video__permission-request__button str-video__permission-request__button--allow", onClick: handleUpdatePermission(request, 'grant'), children: t('common.allow.label', 'Allow') }), jsxRuntime.jsx(Button, { variant: "secondary", appearance: "outline", size: "sm", className: "str-video__permission-request__button str-video__permission-request__button--reject", onClick: handleUpdatePermission(request, 'revoke'), children: t('common.revoke.label', 'Revoke') }), jsxRuntime.jsx(Button, { variant: "secondary", appearance: "outline", size: "sm", className: "str-video__permission-request__button str-video__permission-request__button--reject", onClick: handleUpdatePermission(request, 'dismiss'), children: t('common.dismiss.label', 'Dismiss') })] }, permission))) }, `${user.id}/${reqIndex}`));
2456
2482
  }) }));
2457
2483
  });
2458
- const Button = (props) => {
2459
- const { className, ...rest } = props;
2460
- return (jsxRuntime.jsx("button", { className: clsx('str-video__permission-request__button', className), ...rest }));
2461
- };
2462
2484
  const messageForPermission = (userName, permission, t) => {
2463
2485
  switch (permission) {
2464
2486
  case videoClient.OwnCapability.SEND_AUDIO:
2465
- return t('{{ userName }} is requesting to speak', { userName });
2487
+ return t('permissions.requestingToSpeak.text', '{{ userName }} is requesting to speak', { userName });
2466
2488
  case videoClient.OwnCapability.SEND_VIDEO:
2467
- return t('{{ userName }} is requesting to share their camera', {
2489
+ return t('permissions.requestingCamera.text', '{{ userName }} is requesting to share their camera', {
2468
2490
  userName,
2469
2491
  });
2470
2492
  case videoClient.OwnCapability.SCREENSHARE:
2471
- return t('{{ userName }} is requesting to present their screen', {
2493
+ return t('permissions.requestingScreenShare.text', '{{ userName }} is requesting to present their screen', {
2472
2494
  userName,
2473
2495
  });
2474
2496
  default:
2475
- return t('{{ userName }} is requesting permission: {{ permission }}', {
2497
+ return t('permissions.requestingPermission.text', '{{ userName }} is requesting permission: {{ permission }}', {
2476
2498
  userName,
2477
2499
  permission,
2478
2500
  });
2479
2501
  }
2480
2502
  };
2481
2503
 
2482
- const StreamTheme = ({ as: Component = 'div', className, children, ...props }) => {
2483
- return (jsxRuntime.jsx(Component, { ...props, className: clsx('str-video', className), children: children }));
2504
+ const StreamTheme = ({ as: Component = 'div', className, children, theme = 'str-video__theme-dark', ...props }) => {
2505
+ return (jsxRuntime.jsx(Component, { ...props, className: clsx('str-video', theme, className), children: children }));
2484
2506
  };
2485
2507
 
2486
2508
  const DefaultDisabledVideoPreview = () => {
2487
- const { t } = videoReactBindings.useI18n();
2488
- return (jsxRuntime.jsx("div", { className: "str_video__video-preview__disabled-video-preview", children: t('Video is disabled') }));
2509
+ const { t } = useI18n();
2510
+ return (jsxRuntime.jsx("div", { className: "str_video__video-preview__disabled-video-preview", children: t('common.videoDisabled.text', 'Video is disabled') }));
2489
2511
  };
2490
2512
  const DefaultNoCameraPreview = () => {
2491
- const { t } = videoReactBindings.useI18n();
2492
- return (jsxRuntime.jsx("div", { className: "str_video__video-preview__no-camera-preview", children: t('No camera found') }));
2513
+ const { t } = useI18n();
2514
+ return (jsxRuntime.jsx("div", { className: "str_video__video-preview__no-camera-preview", children: t('common.noCameraFound.text', 'No camera found') }));
2515
+ };
2516
+ /**
2517
+ * Reports whether the local user is currently speaking, before they join the
2518
+ * call. `MicrophoneManager` only runs its detector while the microphone is
2519
+ * muted (for `speakingWhileMuted`), so this mirrors `AudioVolumeIndicator` and
2520
+ * runs one on the live microphone stream instead.
2521
+ */
2522
+ const useLocalSpeaking = (enabled) => {
2523
+ const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
2524
+ const { isEnabled, mediaStream } = useMicrophoneState();
2525
+ const [isSpeaking, setIsSpeaking] = react.useState(false);
2526
+ react.useEffect(() => {
2527
+ if (!enabled || !isEnabled || !mediaStream) {
2528
+ setIsSpeaking(false);
2529
+ return;
2530
+ }
2531
+ const dispose = videoClient.createSoundDetector(mediaStream, ({ isSoundDetected }) => setIsSpeaking(isSoundDetected), { detectionFrequencyInMs: 80, destroyStreamOnStop: false });
2532
+ return () => {
2533
+ setIsSpeaking(false);
2534
+ dispose().catch(console.error);
2535
+ };
2536
+ }, [enabled, isEnabled, mediaStream]);
2537
+ return isSpeaking;
2493
2538
  };
2494
- const VideoPreview = ({ className, mirror = true, DisabledVideoPreview = DefaultDisabledVideoPreview, NoCameraPreview = DefaultNoCameraPreview, StartingCameraPreview = LoadingIndicator, }) => {
2539
+ const VideoPreview = ({ className, mirror = true, DisabledVideoPreview = DefaultDisabledVideoPreview, NoCameraPreview = DefaultNoCameraPreview, StartingCameraPreview = LoadingIndicator, speakingIndicatorVisible = false, }) => {
2495
2540
  const { useCameraState } = videoReactBindings.useCallStateHooks();
2496
2541
  const { devices, status, isMute, mediaStream } = useCameraState();
2542
+ const isSpeaking = useLocalSpeaking(speakingIndicatorVisible);
2497
2543
  let contents;
2498
2544
  if (isMute && devices?.length === 0) {
2499
2545
  contents = jsxRuntime.jsx(NoCameraPreview, {});
@@ -2508,21 +2554,43 @@ const VideoPreview = ({ className, mirror = true, DisabledVideoPreview = Default
2508
2554
  else {
2509
2555
  contents = jsxRuntime.jsx(DisabledVideoPreview, {});
2510
2556
  }
2511
- return (jsxRuntime.jsx("div", { className: clsx('str-video__video-preview-container', className), children: contents }));
2557
+ return (jsxRuntime.jsx("div", { className: clsx('str-video__video-preview-container', className, {
2558
+ 'str-video__video-preview-container--speaking': isSpeaking,
2559
+ }), children: contents }));
2560
+ };
2561
+
2562
+ /** How many of the three bars are lit, per quality level. */
2563
+ const ACTIVE_BARS = {
2564
+ [videoClient.SfuModels.ConnectionQuality.POOR]: 1,
2565
+ [videoClient.SfuModels.ConnectionQuality.GOOD]: 2,
2566
+ [videoClient.SfuModels.ConnectionQuality.EXCELLENT]: 3,
2567
+ };
2568
+ const BAR_COUNT = 3;
2569
+ /**
2570
+ * Three ascending bars showing a participant's connection quality.
2571
+ */
2572
+ const ConnectionQualityIndicator = ({ quality, title, className, }) => {
2573
+ const activeBars = ACTIVE_BARS[quality];
2574
+ if (!activeBars)
2575
+ return null;
2576
+ const level = videoClient.SfuModels.ConnectionQuality[quality].toLowerCase();
2577
+ return (jsxRuntime.jsx("span", { className: clsx('str-video__connection-quality-indicator', `str-video__connection-quality-indicator--${level}`, className), title: title ?? level, children: Array.from({ length: BAR_COUNT }, (_, index) => (jsxRuntime.jsx("span", { className: clsx('str-video__connection-quality-indicator__bar', index < activeBars &&
2578
+ 'str-video__connection-quality-indicator__bar--active') }, index))) }));
2512
2579
  };
2513
2580
 
2514
2581
  const ToggleButton = react.forwardRef(function ToggleButtonRender(props, ref) {
2515
- return jsxRuntime.jsx(IconButton, { enabled: props.menuShown, icon: "ellipsis", ref: ref });
2582
+ const { t } = useI18n();
2583
+ return (jsxRuntime.jsx(IconButton, { className: "str-video__participant-view__menu-button", active: props.menuShown, size: "sm", variant: "secondary", ref: ref, title: t('participantView.moreOptions.title', 'More options'), icon: "ellipsis" }));
2516
2584
  });
2517
2585
  const DefaultScreenShareOverlay = () => {
2518
2586
  const call = videoReactBindings.useCall();
2519
- const { t } = videoReactBindings.useI18n();
2587
+ const { t } = useI18n();
2520
2588
  const stopScreenShare = () => {
2521
2589
  call?.screenShare.disable().catch((err) => {
2522
2590
  console.error('Failed to stop screen sharing:', err);
2523
2591
  });
2524
2592
  };
2525
- return (jsxRuntime.jsxs("div", { className: "str-video__screen-share-overlay", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__screen-share-overlay__title", children: t('You are presenting your screen') }), jsxRuntime.jsxs("button", { onClick: stopScreenShare, type: "button", className: "str-video__screen-share-overlay__button", children: [jsxRuntime.jsx(Icon, { icon: "close" }), " ", t('Stop Screen Sharing')] })] }));
2593
+ return (jsxRuntime.jsxs("div", { className: "str-video__screen-share-overlay", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__screen-share-overlay__title", children: t('common.presentingYourScreen.text', 'You are presenting your screen') }), jsxRuntime.jsxs(Button, { variant: "destructive", onClick: stopScreenShare, className: "str-video__screen-share-overlay__button", children: [jsxRuntime.jsx(Icon, { icon: "close" }), ' ', t('participantView.stopScreenSharing.label', 'Stop Screen Sharing')] })] }));
2526
2594
  };
2527
2595
  /**
2528
2596
  * Rendered over a remote participant's tile when the browser's autoplay policy
@@ -2531,8 +2599,8 @@ const DefaultScreenShareOverlay = () => {
2531
2599
  */
2532
2600
  const DefaultAudioBlockedNotification = () => {
2533
2601
  const call = videoReactBindings.useCall();
2534
- const { t } = videoReactBindings.useI18n();
2535
- return (jsxRuntime.jsx("button", { type: "button", className: "str-video__audio-blocked-notification", onClick: call?.resumeAudio, "aria-live": "polite", children: jsxRuntime.jsxs("span", { className: "str-video__audio-blocked-notification__message", children: [jsxRuntime.jsx(Icon, { icon: "speaker" }), jsxRuntime.jsxs("span", { className: "str-video__audio-blocked-notification__text", children: [jsxRuntime.jsx("span", { className: "str-video__audio-blocked-notification__title", children: t('Audio is blocked by your system') }), jsxRuntime.jsx("span", { className: "str-video__audio-blocked-notification__subtitle", children: t('Click or tap to play audio') })] })] }) }));
2602
+ const { t } = useI18n();
2603
+ return (jsxRuntime.jsx("button", { type: "button", className: "str-video__audio-blocked-notification", onClick: call?.resumeAudio, "aria-live": "polite", children: jsxRuntime.jsxs("span", { className: "str-video__audio-blocked-notification__message", children: [jsxRuntime.jsx(Icon, { icon: "speaker" }), jsxRuntime.jsxs("span", { className: "str-video__audio-blocked-notification__text", children: [jsxRuntime.jsx("span", { className: "str-video__audio-blocked-notification__title", children: t('participantView.audioBlocked.title', 'Audio is blocked by your system') }), jsxRuntime.jsx("span", { className: "str-video__audio-blocked-notification__subtitle", children: t('participantView.audioBlocked.description', 'Click or tap to play audio') })] })] }) }));
2536
2604
  };
2537
2605
  const DefaultParticipantViewUI = ({ indicatorsVisible = true, menuPlacement = 'bottom-start', showMenuButton = true, ParticipantActionsContextMenu: ParticipantActionsContextMenu$1 = ParticipantActionsContextMenu, AudioBlockedNotification = DefaultAudioBlockedNotification, }) => {
2538
2606
  const { participant, trackType } = useParticipantViewContext();
@@ -2553,17 +2621,17 @@ const ParticipantDetails = ({ indicatorsVisible = true, }) => {
2553
2621
  const { participant, trackType } = useParticipantViewContext();
2554
2622
  const { isLocalParticipant, connectionQuality, pin, sessionId, name, userId, } = participant;
2555
2623
  const call = videoReactBindings.useCall();
2556
- const { t } = videoReactBindings.useI18n();
2557
- const connectionQualityAsString = !!connectionQuality &&
2558
- videoClient.SfuModels.ConnectionQuality[connectionQuality].toLowerCase();
2624
+ const { t } = useI18n();
2559
2625
  const hasAudioTrack = videoClient.hasAudio(participant);
2560
2626
  const hasVideoTrack = videoClient.hasVideo(participant);
2561
2627
  const canUnpin = !!pin && pin.isLocalPin;
2562
2628
  const isTrackPaused = trackType !== 'none' ? videoClient.hasPausedTrack(participant, trackType) : false;
2563
2629
  const isAudioConnecting = videoReactBindings.useIsAudioConnecting(participant);
2564
2630
  const isVideoConnecting = videoReactBindings.useIsVideoConnecting(participant);
2565
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "str-video__participant-details", children: jsxRuntime.jsxs("div", { className: "str-video__participant-details__name", children: [name || userId, indicatorsVisible && isAudioConnecting && (jsxRuntime.jsx(LoadingIndicator, { className: "str-video__participant-details__name--audio-connecting", tooltip: t('Audio is connecting...') })), indicatorsVisible && !hasAudioTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--audio-muted" })), indicatorsVisible && isVideoConnecting && (jsxRuntime.jsx(LoadingIndicator, { className: "str-video__participant-details__name--video-connecting", tooltip: t('Video is connecting...') })), indicatorsVisible && !hasVideoTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--video-muted" })), indicatorsVisible && isTrackPaused && (jsxRuntime.jsx("span", { title: t('Video paused due to insufficient bandwidth'), className: "str-video__participant-details__name--track-paused" })), indicatorsVisible && pin && (jsxRuntime.jsx("span", { title: canUnpin ? t('Unpin') : t('Pinned'), onClick: canUnpin ? () => call?.unpin(sessionId) : undefined, className: "str-video__participant-details__name--pinned" })), indicatorsVisible && jsxRuntime.jsx(SpeechIndicator, {})] }) }), indicatorsVisible && (jsxRuntime.jsx(Notification, { isVisible: isLocalParticipant &&
2566
- connectionQuality === videoClient.SfuModels.ConnectionQuality.POOR, message: t('Poor connection quality'), children: connectionQualityAsString && (jsxRuntime.jsx("span", { className: clsx('str-video__participant-details__connection-quality', `str-video__participant-details__connection-quality--${connectionQualityAsString}`), title: connectionQualityAsString })) }))] }));
2631
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "str-video__participant-details", children: jsxRuntime.jsxs("div", { className: "str-video__participant-details__name", children: [jsxRuntime.jsx("span", { className: "str-video__participant-details__name-text", children: name || userId }), indicatorsVisible && isAudioConnecting && (jsxRuntime.jsx(LoadingIndicator, { className: "str-video__participant-details__name--audio-connecting", tooltip: t('participantView.audioConnecting.title', 'Audio is connecting...') })), indicatorsVisible && !hasAudioTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--audio-muted" })), indicatorsVisible && isVideoConnecting && (jsxRuntime.jsx(LoadingIndicator, { className: "str-video__participant-details__name--video-connecting", tooltip: t('participantView.videoConnecting.title', 'Video is connecting...') })), indicatorsVisible && !hasVideoTrack && (jsxRuntime.jsx("span", { className: "str-video__participant-details__name--video-muted" })), indicatorsVisible && isTrackPaused && (jsxRuntime.jsx("span", { title: t('participantView.videoPausedInsufficientBandwidth.title', 'Video paused due to insufficient bandwidth'), className: "str-video__participant-details__name--track-paused" })), indicatorsVisible && pin && (jsxRuntime.jsx("span", { title: canUnpin
2632
+ ? t('participantView.unpin.label', 'Unpin')
2633
+ : t('participantList.pinned.title', 'Pinned'), onClick: canUnpin ? () => call?.unpin(sessionId) : undefined, className: "str-video__participant-details__name--pinned" })), indicatorsVisible && jsxRuntime.jsx(SpeechIndicator, {})] }) }), indicatorsVisible && (jsxRuntime.jsx(Notification, { state: "error", isVisible: isLocalParticipant &&
2634
+ connectionQuality === videoClient.SfuModels.ConnectionQuality.POOR, message: t('participantView.connectionQuality.poor.text', 'Poor connection quality. Please check your internet connection.'), children: connectionQuality && (jsxRuntime.jsx(ConnectionQualityIndicator, { quality: connectionQuality })) }))] }));
2567
2635
  };
2568
2636
  const SpeechIndicator = () => {
2569
2637
  const { participant } = useParticipantViewContext();
@@ -2613,7 +2681,7 @@ const ParticipantView = react.memo(react.forwardRef(function ParticipantViewRend
2613
2681
  applyElementToRef(ref, element);
2614
2682
  setTrackedElement(element);
2615
2683
  }, className: clsx('str-video__participant-view', isDominantSpeaker &&
2616
- 'str-video__participant-view--dominant-speaker', isSpeaking && 'str-video__participant-view--speaking', !hasVideoTrack && 'str-video__participant-view--no-video', !hasAudioTrack && 'str-video__participant-view--no-audio', className), children: jsxRuntime.jsxs(ParticipantViewContext.Provider, { value: participantViewContextValue, children: [!isLocalParticipant && !muteAudio && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "audioTrack" })), hasScreenShareAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "screenShareAudioTrack" }))] })), jsxRuntime.jsx(Video$1, { VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, participant: participant, trackType: trackType, refs: videoRefs, enabled: isLocalParticipant ||
2684
+ 'str-video__participant-view--dominant-speaker', isSpeaking && 'str-video__participant-view--speaking', !hasVideoTrack && 'str-video__participant-view--no-video', !hasAudioTrack && 'str-video__participant-view--no-audio', className), children: jsxRuntime.jsxs(ParticipantViewContext.Provider, { value: participantViewContextValue, children: [!isLocalParticipant && !muteAudio && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "audioTrack" })), hasScreenShareAudioTrack && (jsxRuntime.jsx(Audio, { participant: participant, trackType: "screenShareAudioTrack" }))] })), jsxRuntime.jsx(Video, { VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, participant: participant, trackType: trackType, refs: videoRefs, enabled: isLocalParticipant ||
2617
2685
  trackType !== 'videoTrack' ||
2618
2686
  isParticipantVideoEnabled(participant.sessionId), mirror: mirror, autoPlay: true }), isComponentType(ParticipantViewUI) ? (jsxRuntime.jsx(ParticipantViewUI, {})) : (ParticipantViewUI)] }) }));
2619
2687
  }));
@@ -2623,185 +2691,27 @@ ParticipantView.displayName = 'ParticipantView';
2623
2691
  const StreamCall = videoReactBindings.StreamCallProvider;
2624
2692
  StreamCall.displayName = 'StreamCall';
2625
2693
 
2626
- var Joining = "Joining";
2627
- var Mic = "Mic";
2628
- var Ringing = "Ringing";
2629
- var Speakers = "Speakers";
2630
- var Video = "Video";
2631
- var Live = "Live";
2632
- var Reactions = "Reactions";
2633
- var Statistics = "Statistics";
2634
- var Invite = "Invite";
2635
- var Join = "Join";
2636
- var You = "You";
2637
- var Me = "Me";
2638
- var Unknown = "Unknown";
2639
- var Default = "Default";
2640
- var Refresh = "Refresh";
2641
- var Allow = "Allow";
2642
- var Revoke = "Revoke";
2643
- var Dismiss = "Dismiss";
2644
- var Pinned = "Pinned";
2645
- var Unpin = "Unpin";
2646
- var Pin = "Pin";
2647
- var Block = "Block";
2648
- var Kick = "Kick";
2649
- var Enter = "Enter";
2650
- var Leave = "Leave";
2651
- var Participants = "Participants";
2652
- var Anonymous = ", and ({{ count }}) anonymous";
2653
- var Speaker = "Speaker";
2654
- var Microphone = "Microphone";
2655
- var Backstage = "Backstage";
2656
- var en = {
2657
- Joining: Joining,
2658
- Mic: Mic,
2659
- "No internet connection": "No internet connection",
2660
- "Re-connecting": "Re-connecting",
2661
- Ringing: Ringing,
2662
- "Screen Share": "Screen Share",
2663
- "Select a Camera": "Select a Camera",
2664
- "Select a Mic": "Select a Mic",
2665
- "Select Speakers": "Select Speakers",
2666
- Speakers: Speakers,
2667
- Video: Video,
2668
- "You are muted. Unmute to speak.": "You are muted. Unmute to speak.",
2669
- "Background filters performance is degraded. Consider disabling filters for better performance.": "Background filters performance is degraded. Consider disabling filters for better performance.",
2670
- Live: Live,
2671
- "Livestream starts soon": "Livestream starts soon",
2672
- "Livestream starts at {{ time }}": "Livestream starts at {{ time }}",
2673
- "{{ count }} participants joined early_one": "{{ count }} participant joined early",
2674
- "{{ count }} participants joined early_other": "{{ count }} participants joined early",
2675
- "You can now speak.": "You can now speak.",
2676
- "Awaiting for an approval to speak.": "Awaiting for an approval to speak.",
2677
- "You can no longer speak.": "You can no longer speak.",
2678
- "You can now share your video.": "You can now share your video.",
2679
- "Awaiting for an approval to share your video.": "Awaiting for an approval to share your video.",
2680
- "You can no longer share your video.": "You can no longer share your video.",
2681
- "Waiting for recording to stop...": "Waiting for recording to stop...",
2682
- "Waiting for recording to start...": "Waiting for recording to start...",
2683
- "Record call": "Record call",
2684
- Reactions: Reactions,
2685
- Statistics: Statistics,
2686
- "You can now share your screen.": "You can now share your screen.",
2687
- "Awaiting for an approval to share screen.": "Awaiting for an approval to share screen.",
2688
- "You can no longer share your screen.": "You can no longer share your screen.",
2689
- "Share screen": "Share screen",
2690
- "Incoming Call...": "Incoming Call...",
2691
- "Calling...": "Calling...",
2692
- "Mute All": "Mute All",
2693
- Invite: Invite,
2694
- Join: Join,
2695
- You: You,
2696
- Me: Me,
2697
- Unknown: Unknown,
2698
- "Toggle device menu": "Toggle device menu",
2699
- Default: Default,
2700
- "Call Recordings": "Call Recordings",
2701
- Refresh: Refresh,
2702
- "Check your browser video permissions": "Check your browser video permissions",
2703
- "Video publishing is disabled by the system": "Video publishing is disabled by the system",
2704
- "You have no permission to share your video": "You have no permission to share your video",
2705
- "You have no permission to share your audio": "You have no permission to share your audio",
2706
- "You are presenting your screen": "You are presenting your screen",
2707
- "Stop Screen Sharing": "Stop Screen Sharing",
2708
- "Audio is blocked by your system": "Audio is blocked by your system",
2709
- "Click or tap to play audio": "Click or tap to play audio",
2710
- Allow: Allow,
2711
- Revoke: Revoke,
2712
- Dismiss: Dismiss,
2713
- "Microphone on": "Microphone on",
2714
- "Microphone off": "Microphone off",
2715
- "Microphone is paused by your system": "Microphone is paused by your system",
2716
- "Camera on": "Camera on",
2717
- "Camera off": "Camera off",
2718
- "Camera is paused by your system": "Camera is paused by your system",
2719
- "No camera found": "No camera found",
2720
- "Video is disabled": "Video is disabled",
2721
- Pinned: Pinned,
2722
- Unpin: Unpin,
2723
- Pin: Pin,
2724
- "Pin for everyone": "Pin for everyone",
2725
- "Unpin for everyone": "Unpin for everyone",
2726
- Block: Block,
2727
- Kick: Kick,
2728
- "Turn off video": "Turn off video",
2729
- "Turn off screen share": "Turn off screen share",
2730
- "Mute audio": "Mute audio",
2731
- "Mute screen share audio": "Mute screen share audio",
2732
- "Allow audio": "Allow audio",
2733
- "Allow video": "Allow video",
2734
- "Allow screen sharing": "Allow screen sharing",
2735
- "Disable audio": "Disable audio",
2736
- "Disable video": "Disable video",
2737
- "Disable screen sharing": "Disable screen sharing",
2738
- Enter: Enter,
2739
- Leave: Leave,
2740
- "Leave call": "Leave call",
2741
- "End call for all": "End call for all",
2742
- "{{ direction }} fullscreen": "{{ direction }} fullscreen",
2743
- "{{ direction }} picture-in-picture": "{{ direction }} picture-in-picture",
2744
- "Dominant Speaker": "Dominant Speaker",
2745
- "Poor connection quality": "Poor connection quality. Please check your internet connection.",
2746
- "Video paused due to insufficient bandwidth": "Video paused due to insufficient bandwidth",
2747
- "Audio is connecting...": "Audio is connecting...",
2748
- "Video is connecting...": "Video is connecting...",
2749
- Participants: Participants,
2750
- Anonymous: Anonymous,
2751
- "No participants found": "No participants found",
2752
- "Participants ({{ numberOfParticipants }})": "Participants ({{ numberOfParticipants }})",
2753
- "{{ userName }} is sharing their screen": "{{ userName }} is sharing their screen",
2754
- "{{ userName }} is requesting to speak": "{{ userName }} is requesting to speak",
2755
- "{{ userName }} is requesting to share their camera": "{{ userName }} is requesting to share their camera",
2756
- "{{ userName }} is requesting to present their screen": "{{ userName }} is requesting to present their screen",
2757
- "{{ userName }} is requesting permission: {{ permission }}": "{{ userName }} is requesting permission: {{ permission }}",
2758
- "Applying...": "Applying...",
2759
- "Disable blur": "Disable blur",
2760
- "Blur background": "Blur background",
2761
- "Migrating...": "Migrating...",
2762
- "Reconnecting...": "Reconnecting...",
2763
- "You are offline. Check your internet connection.": "You are offline. Check your internet connection.",
2764
- "Failed to restore connection. Please try again.": "Failed to restore connection. Please try again.",
2765
- "Failed to join. Please try again.": "Failed to join. Please try again.",
2766
- "Set up your call before joining": "Set up your call before joining",
2767
- "Please grant your browser permission to access your camera and microphone.": "Please grant your browser permission to access your camera and microphone.",
2768
- "Start call": "Start call",
2769
- Speaker: Speaker,
2770
- Microphone: Microphone,
2771
- Backstage: Backstage,
2772
- "Go Live": "Go Live",
2773
- "Stop Live": "End Live",
2774
- "Enter Backstage": "Enter Backstage",
2775
- "Prepare your livestream": "Prepare your livestream",
2776
- "Ready to go live": "Ready to go live",
2777
- "Stream is ready!": "Stream is ready!",
2778
- "Waiting for the livestream to start": "Waiting for the livestream to start",
2779
- "{{ count }} waiting": "{{ count }} waiting",
2780
- "Join Stream": "Join Stream",
2781
- "Join automatically when stream starts": "Join automatically when stream starts",
2782
- "Display name": "Display name",
2783
- "Permission needed": "Permission needed",
2784
- "Call ended": "Call ended",
2785
- "Rejoin call": "Rejoin call",
2786
- "Left by mistake?": "Left by mistake?",
2787
- "Help us improve": "Help us improve",
2788
- "Leave feedback": "Leave feedback",
2789
- "Failed to rejoin. Please try again.": "Failed to rejoin. Please try again.",
2790
- "Share your feedback": "Share your feedback",
2791
- "Tell us about your experience...": "Tell us about your experience...",
2792
- "Submit feedback": "Submit feedback",
2793
- "Feedback message": "Feedback message",
2794
- "How was your call quality?": "How was your call quality?",
2795
- "Rate {{ count }} star_one": "Rate {{ count }} star",
2796
- "Rate {{ count }} star_other": "Rate {{ count }} stars",
2797
- "Thank you!": "Thank you!",
2798
- "Your feedback helps improve call quality.": "Your feedback helps improve call quality."
2799
- };
2800
-
2801
- const translations = { en };
2802
-
2803
- const StreamVideo = (props) => {
2804
- return (jsxRuntime.jsx(videoReactBindings.StreamVideoProvider, { translationsOverrides: translations, ...props }));
2694
+ /**
2695
+ * StreamVideo is the root provider of the SDK: it provides the client to the component tree and
2696
+ * mounts the SDK's translation context.
2697
+ *
2698
+ * The i18n runtime lives here rather than in `@stream-io/video-react-bindings` because the key
2699
+ * catalog is generated from *this* package's `t()` call sites.
2700
+ *
2701
+ * This also fixes a real bug in the previous implementation, which rendered
2702
+ * `<StreamVideoProvider translationsOverrides={translations} {...props} />`. Because `{...props}`
2703
+ * came last, an integrator passing `translationsOverrides` *replaced* the SDK's bundled strings
2704
+ * instead of merging with them, leaving every untranslated key rendering as a raw dotted path.
2705
+ * `useStreami18n` layers integrator dictionaries over the SDK's own defaults, so a partial
2706
+ * dictionary is now safe.
2707
+ */
2708
+ const StreamVideo = ({ children, client, i18nInstance, language, translations, }) => {
2709
+ const translationContext = useStreami18n({
2710
+ i18nInstance,
2711
+ language,
2712
+ translations,
2713
+ });
2714
+ return (jsxRuntime.jsx(videoReactBindings.StreamVideoProvider, { client: client, children: jsxRuntime.jsx(TranslationProvider, { value: translationContext, children: children }) }));
2805
2715
  };
2806
2716
  StreamVideo.displayName = 'StreamVideo';
2807
2717
 
@@ -2982,7 +2892,7 @@ const BackstageLayout = (props) => {
2982
2892
  const { useParticipantCount, useCallStartsAt } = videoReactBindings.useCallStateHooks();
2983
2893
  const participantCount = useParticipantCount();
2984
2894
  const startsAt = useCallStartsAt();
2985
- const { t } = videoReactBindings.useI18n();
2895
+ const { t } = useI18n();
2986
2896
  const [startsAtPassed, setStartsAtPassed] = react.useState(() => !!startsAt && startsAt.getTime() < Date.now());
2987
2897
  react.useEffect(() => {
2988
2898
  if (!startsAt || startsAtPassed)
@@ -2996,17 +2906,20 @@ const BackstageLayout = (props) => {
2996
2906
  return () => clearInterval(interval);
2997
2907
  }, [startsAt, startsAtPassed]);
2998
2908
  return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__wrapper", children: jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__backstage", children: [startsAt && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__starts-at", children: startsAtPassed
2999
- ? t('Livestream starts soon')
3000
- : t('Livestream starts at {{ time }}', {
2909
+ ? t('livestream.backstage.startsSoon.text', 'Livestream starts soon')
2910
+ : t('livestream.backstage.startsAt.text', 'Livestream starts at {{ time }}', {
3001
2911
  time: startsAt.toLocaleTimeString([], {
3002
2912
  hour: '2-digit',
3003
2913
  minute: '2-digit',
3004
2914
  }),
3005
- }) })), showEarlyParticipantCount && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__early-viewers-count", children: t('{{ count }} participants joined early', {
3006
- count: humanizeParticipantCount
3007
- ? videoClient.humanize(participantCount)
3008
- : participantCount,
3009
- }) }))] }) }));
2915
+ }) })), showEarlyParticipantCount && (jsxRuntime.jsxs("span", { className: "str-video__livestream-layout__early-viewers-count", children: [jsxRuntime.jsx(Icon, { icon: "livestream-viewers" }), t('livestream.backstage.participantsJoinedEarly.text', {
2916
+ count: participantCount,
2917
+ formattedCount: humanizeParticipantCount
2918
+ ? videoClient.humanize(participantCount)
2919
+ : participantCount,
2920
+ defaultValue_one: '{{ formattedCount }} participant joined early',
2921
+ defaultValue_other: '{{ formattedCount }} participants joined early',
2922
+ })] }))] }) }));
3010
2923
  };
3011
2924
  BackstageLayout.displayName = 'BackstageLayout';
3012
2925
  const ParticipantOverlay = (props) => {
@@ -3024,14 +2937,14 @@ const ParticipantOverlay = (props) => {
3024
2937
  const toggleFullScreen = useToggleFullScreen();
3025
2938
  const { speaker, volume } = useSpeakerState();
3026
2939
  const isSpeakerMuted = volume === 0;
3027
- const { t } = videoReactBindings.useI18n();
3028
- return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar-left", children: [showLiveBadge && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__live-badge", children: t('Live') })), showParticipantCount && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__viewers-count", children: humanizeParticipantCount
3029
- ? videoClient.humanize(participantCount)
3030
- : participantCount })), showSpeakerName && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__speaker-name", title: participant.name || participant.userId || '', children: participant.name || participant.userId || '' }))] }), jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay__bar-center", children: showDuration && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__duration", children: formatDuration(duration) })) }), jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar-right", children: [showMuteButton && (jsxRuntime.jsx("span", { className: clsx('str-video__livestream-layout__mute-button', isSpeakerMuted &&
2940
+ const { t } = useI18n();
2941
+ return (jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay", children: overlayBarVisible && (jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar", children: [jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar-left", children: [showLiveBadge && (jsxRuntime.jsxs("span", { className: "str-video__livestream-layout__live-badge", children: [jsxRuntime.jsx("span", { className: "str-video__livestream-layout__live-badge__dot", "aria-hidden": "true" }), t('common.live.label', 'Live')] })), showParticipantCount && (jsxRuntime.jsxs("span", { className: "str-video__livestream-layout__viewers-count", children: [jsxRuntime.jsx(Icon, { icon: "livestream-viewers" }), humanizeParticipantCount
2942
+ ? videoClient.humanize(participantCount)
2943
+ : participantCount] })), showSpeakerName && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__speaker-name", title: participant.name || participant.userId || '', children: jsxRuntime.jsx("span", { children: participant.name || participant.userId || '' }) }))] }), jsxRuntime.jsx("div", { className: "str-video__livestream-layout__overlay__bar-center", children: showDuration && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__duration", children: formatDuration(duration) })) }), jsxRuntime.jsxs("div", { className: "str-video__livestream-layout__overlay__bar-right", children: [showMuteButton && (jsxRuntime.jsx(IconButton, { icon: isSpeakerMuted ? 'speaker-off' : 'speaker', variant: "secondary", appearance: "solid", size: "sm", className: clsx('str-video__livestream-layout__mute-button', isSpeakerMuted &&
3031
2944
  'str-video__livestream-layout__mute-button--muted'), onClick: () => speaker.setVolume(isSpeakerMuted ? 1 : 0) })), enableFullScreen &&
3032
2945
  participantViewElement &&
3033
2946
  typeof participantViewElement.requestFullscreen !==
3034
- 'undefined' && (jsxRuntime.jsx("span", { className: "str-video__livestream-layout__go-fullscreen", onClick: toggleFullScreen }))] })] })) }));
2947
+ 'undefined' && (jsxRuntime.jsx(IconButton, { icon: "fullscreen", variant: "secondary", appearance: "solid", size: "sm", className: "str-video__livestream-layout__go-fullscreen", onClick: toggleFullScreen }))] })] })) }));
3035
2948
  };
3036
2949
  const useUpdateCallDuration = () => {
3037
2950
  const { useIsCallLive, useCallSession } = videoReactBindings.useCallStateHooks();
@@ -3096,6 +3009,7 @@ const PaginatedGridLayoutGroup = ({ group, mirror, VideoPlaceholder, PictureInPi
3096
3009
  }), children: group.map((participant) => (jsxRuntime.jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, PictureInPicturePlaceholder: PictureInPicturePlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId))) }));
3097
3010
  };
3098
3011
  const PaginatedGridLayout = (props) => {
3012
+ const { t } = useI18n();
3099
3013
  const { groupSize = (props.groupSize || 0) > 0
3100
3014
  ? props.groupSize || GROUP_SIZE
3101
3015
  : GROUP_SIZE, excludeLocalParticipant = false, filterParticipants, mirrorLocalParticipantVideo = true, pageArrowsVisible = true, VideoPlaceholder, ParticipantViewUI = DefaultParticipantViewUI, PictureInPicturePlaceholder, muted, } = props;
@@ -3127,7 +3041,7 @@ const PaginatedGridLayout = (props) => {
3127
3041
  const mirror = mirrorLocalParticipantVideo ? undefined : false;
3128
3042
  if (!call)
3129
3043
  return null;
3130
- return (jsxRuntime.jsxs("div", { className: "str-video__paginated-grid-layout__wrapper", ref: setPaginatedGridLayoutWrapperElement, children: [!muted && jsxRuntime.jsx(ParticipantsAudio, { participants: remoteParticipants }), jsxRuntime.jsxs("div", { className: "str-video__paginated-grid-layout", children: [pageArrowsVisible && pageCount > 1 && (jsxRuntime.jsx(IconButton, { icon: "caret-left", disabled: page === 0, onClick: () => setPage((currentPage) => Math.max(0, currentPage - 1)) })), selectedGroup && (jsxRuntime.jsx(PaginatedGridLayoutGroup, { group: selectedGroup, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI, PictureInPicturePlaceholder: PictureInPicturePlaceholder })), pageArrowsVisible && pageCount > 1 && (jsxRuntime.jsx(IconButton, { disabled: page === pageCount - 1, icon: "caret-right", onClick: () => setPage((currentPage) => Math.min(pageCount - 1, currentPage + 1)) }))] })] }));
3044
+ return (jsxRuntime.jsxs("div", { className: "str-video__paginated-grid-layout__wrapper", ref: setPaginatedGridLayoutWrapperElement, children: [!muted && jsxRuntime.jsx(ParticipantsAudio, { participants: remoteParticipants }), jsxRuntime.jsxs("div", { className: "str-video__paginated-grid-layout", children: [pageArrowsVisible && pageCount > 1 && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", icon: "caret-left", "aria-label": t('callLayout.paginatedGridLayout.previousPage.ariaLabel', 'Previous page'), disabled: page === 0, onClick: () => setPage((currentPage) => Math.max(0, currentPage - 1)) })), selectedGroup && (jsxRuntime.jsx(PaginatedGridLayoutGroup, { group: selectedGroup, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI, PictureInPicturePlaceholder: PictureInPicturePlaceholder })), pageArrowsVisible && pageCount > 1 && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", disabled: page === pageCount - 1, icon: "caret-right", "aria-label": t('callLayout.paginatedGridLayout.nextPage.ariaLabel', 'Next page'), onClick: () => setPage((currentPage) => Math.min(pageCount - 1, currentPage + 1)) }))] })] }));
3131
3045
  };
3132
3046
  PaginatedGridLayout.displayName = 'PaginatedGridLayout';
3133
3047
 
@@ -3232,6 +3146,7 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
3232
3146
  };
3233
3147
  SpeakerLayout.displayName = 'SpeakerLayout';
3234
3148
  const HorizontalScrollButtons = ({ scrollWrapper, }) => {
3149
+ const { t } = useI18n();
3235
3150
  const scrollPosition = useHorizontalScrollPosition(scrollWrapper);
3236
3151
  const scrollStartClickHandler = () => {
3237
3152
  scrollWrapper?.scrollBy({ left: -150, behavior: 'smooth' });
@@ -3239,9 +3154,10 @@ const HorizontalScrollButtons = ({ scrollWrapper, }) => {
3239
3154
  const scrollEndClickHandler = () => {
3240
3155
  scrollWrapper?.scrollBy({ left: 150, behavior: 'smooth' });
3241
3156
  };
3242
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'start' && (jsxRuntime.jsx(IconButton, { onClick: scrollStartClickHandler, icon: "caret-left", className: "str-video__speaker-layout__participants-bar--button-left" })), scrollPosition && scrollPosition !== 'end' && (jsxRuntime.jsx(IconButton, { onClick: scrollEndClickHandler, icon: "caret-right", className: "str-video__speaker-layout__participants-bar--button-right" }))] }));
3157
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'start' && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", onClick: scrollStartClickHandler, icon: "caret-left", "aria-label": t('callLayout.speakerLayout.scrollParticipantsLeft.ariaLabel', 'Scroll participants left'), className: "str-video__speaker-layout__participants-bar--button-left" })), scrollPosition && scrollPosition !== 'end' && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", onClick: scrollEndClickHandler, icon: "caret-right", "aria-label": t('callLayout.speakerLayout.scrollParticipantsRight.ariaLabel', 'Scroll participants right'), className: "str-video__speaker-layout__participants-bar--button-right" }))] }));
3243
3158
  };
3244
3159
  const VerticalScrollButtons = ({ scrollWrapper, }) => {
3160
+ const { t } = useI18n();
3245
3161
  const scrollPosition = useVerticalScrollPosition(scrollWrapper);
3246
3162
  const scrollTopClickHandler = () => {
3247
3163
  scrollWrapper?.scrollBy({ top: -150, behavior: 'smooth' });
@@ -3249,11 +3165,11 @@ const VerticalScrollButtons = ({ scrollWrapper, }) => {
3249
3165
  const scrollBottomClickHandler = () => {
3250
3166
  scrollWrapper?.scrollBy({ top: 150, behavior: 'smooth' });
3251
3167
  };
3252
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'top' && (jsxRuntime.jsx(IconButton, { onClick: scrollTopClickHandler, icon: "caret-up", className: "str-video__speaker-layout__participants-bar--button-top" })), scrollPosition && scrollPosition !== 'bottom' && (jsxRuntime.jsx(IconButton, { onClick: scrollBottomClickHandler, icon: "caret-down", className: "str-video__speaker-layout__participants-bar--button-bottom" }))] }));
3168
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'top' && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", onClick: scrollTopClickHandler, icon: "caret-up", "aria-label": t('callLayout.speakerLayout.scrollParticipantsUp.ariaLabel', 'Scroll participants up'), className: "str-video__speaker-layout__participants-bar--button-top" })), scrollPosition && scrollPosition !== 'bottom' && (jsxRuntime.jsx(IconButton, { size: "sm", variant: "secondary", onClick: scrollBottomClickHandler, icon: "caret-down", "aria-label": t('callLayout.speakerLayout.scrollParticipantsDown.ariaLabel', 'Scroll participants down'), className: "str-video__speaker-layout__participants-bar--button-bottom" }))] }));
3253
3169
  };
3254
3170
 
3255
3171
  const Pip = (props) => {
3256
- const { t } = videoReactBindings.useI18n();
3172
+ const { t } = useI18n();
3257
3173
  const { excludeLocalParticipant = false, filterParticipants, mirrorLocalParticipantVideo = true, VideoPlaceholder, ParticipantViewUI = DefaultParticipantViewUI, } = props;
3258
3174
  const [layoutWrapperElement, setLayoutWrapperElement] = react.useState(null);
3259
3175
  const call = videoReactBindings.useCall();
@@ -3272,7 +3188,7 @@ const Pip = (props) => {
3272
3188
  if (!call)
3273
3189
  return null;
3274
3190
  return (jsxRuntime.jsxs("div", { className: "str-video__pip-layout", ref: setLayoutWrapperElement, children: [screenSharingParticipant &&
3275
- (screenSharingParticipant.isLocalParticipant ? (jsxRuntime.jsxs("div", { className: "str-video__pip-screen-share-local", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__pip-screen-share-local__title", children: t('You are presenting your screen') })] })) : (jsxRuntime.jsx(ParticipantView, { participant: screenSharingParticipant, trackType: "screenShareTrack", muteAudio: true, mirror: false, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }))), participants.map((participant) => (jsxRuntime.jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId)))] }));
3191
+ (screenSharingParticipant.isLocalParticipant ? (jsxRuntime.jsxs("div", { className: "str-video__pip-screen-share-local", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__pip-screen-share-local__title", children: t('common.presentingYourScreen.text', 'You are presenting your screen') })] })) : (jsxRuntime.jsx(ParticipantView, { participant: screenSharingParticipant, trackType: "screenShareTrack", muteAudio: true, mirror: false, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }))), participants.map((participant) => (jsxRuntime.jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId)))] }));
3276
3192
  };
3277
3193
  Pip.displayName = 'PipLayout.Pip';
3278
3194
 
@@ -3298,7 +3214,7 @@ const getGridDensity = (count) => {
3298
3214
  * Use this when you need a more structured grid view in PIP mode.
3299
3215
  */
3300
3216
  const Grid = (props) => {
3301
- const { t } = videoReactBindings.useI18n();
3217
+ const { t } = useI18n();
3302
3218
  const { excludeLocalParticipant = false, filterParticipants, mirrorLocalParticipantVideo = true, groupSize = 9, pageArrowsVisible = true, VideoPlaceholder, ParticipantViewUI = DefaultParticipantViewUI, } = props;
3303
3219
  const [page, setPage] = react.useState(0);
3304
3220
  const [wrapperElement, setWrapperElement] = react.useState(null);
@@ -3324,7 +3240,7 @@ const Grid = (props) => {
3324
3240
  if (!call)
3325
3241
  return null;
3326
3242
  return (jsxRuntime.jsxs("div", { className: "str-video__pip-layout str-video__pip-layout--grid", ref: setWrapperElement, children: [screenSharingParticipant &&
3327
- (screenSharingParticipant.isLocalParticipant ? (jsxRuntime.jsxs("div", { className: "str-video__pip-screen-share-local", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__pip-screen-share-local__title", children: t('You are presenting your screen') })] })) : (jsxRuntime.jsx(ParticipantView, { participant: screenSharingParticipant, trackType: "screenShareTrack", muteAudio: true, mirror: false, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }))), jsxRuntime.jsxs("div", { className: "str-video__pip-layout__grid-container", children: [pageArrowsVisible && page > 0 && (jsxRuntime.jsx(IconButton, { icon: "caret-left", onClick: () => setPage((currentPage) => Math.max(0, currentPage - 1)), className: "str-video__pip-layout__pagination-button str-video__pip-layout__pagination-button--left" })), selectedGroup && (jsxRuntime.jsx("div", { className: clsx('str-video__pip-layout__grid', `str-video__pip-layout__grid--${getGridDensity(selectedGroup.length)}`), children: selectedGroup.map((participant) => (jsxRuntime.jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId))) })), pageArrowsVisible && page < pageCount - 1 && (jsxRuntime.jsx(IconButton, { icon: "caret-right", onClick: () => setPage((currentPage) => Math.min(pageCount - 1, currentPage + 1)), className: "str-video__pip-layout__pagination-button str-video__pip-layout__pagination-button--right" }))] })] }));
3243
+ (screenSharingParticipant.isLocalParticipant ? (jsxRuntime.jsxs("div", { className: "str-video__pip-screen-share-local", children: [jsxRuntime.jsx(Icon, { icon: "screen-share-off" }), jsxRuntime.jsx("span", { className: "str-video__pip-screen-share-local__title", children: t('common.presentingYourScreen.text', 'You are presenting your screen') })] })) : (jsxRuntime.jsx(ParticipantView, { participant: screenSharingParticipant, trackType: "screenShareTrack", muteAudio: true, mirror: false, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }))), jsxRuntime.jsxs("div", { className: "str-video__pip-layout__grid-container", children: [pageArrowsVisible && page > 0 && (jsxRuntime.jsx(IconButton, { icon: "caret-left", size: "sm", variant: "secondary", onClick: () => setPage((currentPage) => Math.max(0, currentPage - 1)), className: "str-video__pip-layout__pagination-button str-video__pip-layout__pagination-button--left" })), selectedGroup && (jsxRuntime.jsx("div", { className: clsx('str-video__pip-layout__grid', `str-video__pip-layout__grid--${getGridDensity(selectedGroup.length)}`), children: selectedGroup.map((participant) => (jsxRuntime.jsx(ParticipantView, { participant: participant, muteAudio: true, mirror: mirror, VideoPlaceholder: VideoPlaceholder, ParticipantViewUI: ParticipantViewUI }, participant.sessionId))) })), pageArrowsVisible && page < pageCount - 1 && (jsxRuntime.jsx(IconButton, { icon: "caret-right", size: "sm", variant: "secondary", onClick: () => setPage((currentPage) => Math.min(pageCount - 1, currentPage + 1)), className: "str-video__pip-layout__pagination-button str-video__pip-layout__pagination-button--right" }))] })] }));
3328
3244
  };
3329
3245
  Grid.displayName = 'PipLayout.Grid';
3330
3246
 
@@ -3411,7 +3327,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
3411
3327
  return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
3412
3328
  };
3413
3329
 
3414
- const [major, minor, patch] = ("2.0.0-beta.0").split('.');
3330
+ const [major, minor, patch] = ("2.0.0-beta.1").split('.');
3415
3331
  videoClient.setSdkInfo({
3416
3332
  type: videoClient.SfuModels.SdkType.REACT,
3417
3333
  major,
@@ -3419,6 +3335,22 @@ videoClient.setSdkInfo({
3419
3335
  patch,
3420
3336
  });
3421
3337
 
3338
+ Object.defineProperty(exports, "asDynamicKey", {
3339
+ enumerable: true,
3340
+ get: function () { return i18n.asDynamicKey; }
3341
+ });
3342
+ Object.defineProperty(exports, "defaultDateTimeParser", {
3343
+ enumerable: true,
3344
+ get: function () { return i18n.defaultDateTimeParser; }
3345
+ });
3346
+ Object.defineProperty(exports, "getDateString", {
3347
+ enumerable: true,
3348
+ get: function () { return i18n.getDateString; }
3349
+ });
3350
+ Object.defineProperty(exports, "predefinedFormatters", {
3351
+ enumerable: true,
3352
+ get: function () { return i18n.predefinedFormatters; }
3353
+ });
3422
3354
  exports.AcceptCallButton = AcceptCallButton;
3423
3355
  exports.Audio = Audio;
3424
3356
  exports.AudioVolumeIndicator = AudioVolumeIndicator;
@@ -3426,7 +3358,9 @@ exports.Avatar = Avatar;
3426
3358
  exports.AvatarFallback = AvatarFallback;
3427
3359
  exports.BackgroundFiltersProvider = BackgroundFiltersProvider;
3428
3360
  exports.BackstageLayout = BackstageLayout;
3361
+ exports.Badge = Badge;
3429
3362
  exports.BaseVideo = BaseVideo;
3363
+ exports.Button = Button;
3430
3364
  exports.CallControls = CallControls;
3431
3365
  exports.CallParticipantListing = CallParticipantListing;
3432
3366
  exports.CallParticipantListingItem = CallParticipantListingItem;
@@ -3435,11 +3369,10 @@ exports.CallPreview = CallPreview;
3435
3369
  exports.CallRecordingList = CallRecordingList;
3436
3370
  exports.CallRecordingListHeader = CallRecordingListHeader;
3437
3371
  exports.CallRecordingListItem = CallRecordingListItem;
3438
- exports.CallStats = CallStats;
3439
- exports.CallStatsButton = CallStatsButton;
3440
3372
  exports.CancelCallButton = CancelCallButton;
3441
3373
  exports.CancelCallConfirmButton = CancelCallConfirmButton;
3442
3374
  exports.CompositeButton = CompositeButton;
3375
+ exports.ConnectionQualityIndicator = ConnectionQualityIndicator;
3443
3376
  exports.DefaultAudioBlockedNotification = DefaultAudioBlockedNotification;
3444
3377
  exports.DefaultParticipantViewUI = DefaultParticipantViewUI;
3445
3378
  exports.DefaultReactionsMenu = DefaultReactionsMenu;
@@ -3455,6 +3388,7 @@ exports.DropDownSelectOption = DropDownSelectOption;
3455
3388
  exports.EmptyCallRecordingListing = EmptyCallRecordingListing;
3456
3389
  exports.GenericMenu = GenericMenu;
3457
3390
  exports.GenericMenuButtonItem = GenericMenuButtonItem;
3391
+ exports.GenericMenuSeparator = GenericMenuSeparator;
3458
3392
  exports.Icon = Icon;
3459
3393
  exports.IconButton = IconButton;
3460
3394
  exports.LivestreamLayout = LivestreamLayout;
@@ -3489,28 +3423,29 @@ exports.SpeakerLayout = SpeakerLayout;
3489
3423
  exports.SpeakerTest = SpeakerTest;
3490
3424
  exports.SpeakingWhileMutedNotification = SpeakingWhileMutedNotification;
3491
3425
  exports.SpeechIndicator = SpeechIndicator;
3492
- exports.StatCard = StatCard;
3493
3426
  exports.StreamCall = StreamCall;
3494
3427
  exports.StreamTheme = StreamTheme;
3495
3428
  exports.StreamVideo = StreamVideo;
3496
- exports.TextButton = TextButton;
3429
+ exports.Streami18n = Streami18n;
3497
3430
  exports.ToggleAudioOutputButton = ToggleAudioOutputButton;
3498
3431
  exports.ToggleAudioPreviewButton = ToggleAudioPreviewButton;
3499
3432
  exports.ToggleAudioPublishingButton = ToggleAudioPublishingButton;
3500
3433
  exports.ToggleVideoPreviewButton = ToggleVideoPreviewButton;
3501
3434
  exports.ToggleVideoPublishingButton = ToggleVideoPublishingButton;
3502
3435
  exports.Tooltip = Tooltip;
3503
- exports.Video = Video$1;
3436
+ exports.TranslationContext = TranslationContext;
3437
+ exports.TranslationProvider = TranslationProvider;
3438
+ exports.Video = Video;
3504
3439
  exports.VideoPreview = VideoPreview;
3505
3440
  exports.WithTooltip = WithTooltip;
3506
3441
  exports.applyFilter = applyFilter;
3507
3442
  exports.defaultEmojiReactionMap = defaultEmojiReactionMap;
3508
3443
  exports.defaultReactions = defaultReactions;
3509
- exports.translations = translations;
3510
3444
  exports.useBackgroundFilters = useBackgroundFilters;
3511
3445
  exports.useDeviceList = useDeviceList;
3512
3446
  exports.useFilteredParticipants = useFilteredParticipants;
3513
3447
  exports.useHorizontalScrollPosition = useHorizontalScrollPosition;
3448
+ exports.useI18n = useI18n;
3514
3449
  exports.useLoopbackRecording = useLoopbackRecording;
3515
3450
  exports.useMenuContext = useMenuContext;
3516
3451
  exports.useModeration = useModeration;
@@ -3518,6 +3453,7 @@ exports.useNoiseCancellation = useNoiseCancellation;
3518
3453
  exports.useParticipantViewContext = useParticipantViewContext;
3519
3454
  exports.usePersistedDevicePreferences = usePersistedDevicePreferences;
3520
3455
  exports.useRequestPermission = useRequestPermission;
3456
+ exports.useStreami18n = useStreami18n;
3521
3457
  exports.useTrackElementVisibility = useTrackElementVisibility;
3522
3458
  exports.useVerticalScrollPosition = useVerticalScrollPosition;
3523
3459
  Object.keys(videoClient).forEach(function (k) {