@stream-io/video-react-sdk 0.4.25 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +301 -236
  2. package/README.md +5 -5
  3. package/dist/css/styles.css +952 -481
  4. package/dist/css/styles.css.map +1 -1
  5. package/dist/index.cjs.js +946 -639
  6. package/dist/index.cjs.js.map +1 -1
  7. package/dist/index.es.js +939 -639
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/components/Button/CompositeButton.d.ts +9 -11
  10. package/dist/src/components/Button/index.d.ts +0 -1
  11. package/dist/src/components/CallControls/CallStatsButton.d.ts +3 -0
  12. package/dist/src/components/CallControls/CancelCallButton.d.ts +1 -0
  13. package/dist/src/components/CallControls/ReactionsButton.d.ts +2 -1
  14. package/dist/src/components/CallControls/RecordCallButton.d.ts +4 -1
  15. package/dist/src/components/CallControls/ToggleAudioButton.d.ts +3 -9
  16. package/dist/src/components/CallControls/ToggleAudioOutputButton.d.ts +2 -5
  17. package/dist/src/components/CallControls/ToggleVideoButton.d.ts +3 -9
  18. package/dist/src/components/CallParticipantsList/CallParticipantListHeader.d.ts +3 -1
  19. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.d.ts +0 -5
  20. package/dist/src/components/CallStats/CallStats.d.ts +25 -2
  21. package/dist/src/components/DeviceSettings/DeviceSelector.d.ts +6 -1
  22. package/dist/src/components/DeviceSettings/DeviceSelectorAudio.d.ts +4 -2
  23. package/dist/src/components/DeviceSettings/DeviceSelectorVideo.d.ts +2 -1
  24. package/dist/src/components/DeviceSettings/DeviceSettings.d.ts +5 -1
  25. package/dist/src/components/DropdownSelect/DropdownSelect.d.ts +14 -0
  26. package/dist/src/components/DropdownSelect/index.d.ts +1 -0
  27. package/dist/src/components/Icon/Icon.d.ts +2 -1
  28. package/dist/src/components/Menu/GenericMenu.d.ts +4 -2
  29. package/dist/src/components/Menu/MenuToggle.d.ts +15 -2
  30. package/dist/src/components/Notification/Notification.d.ts +1 -0
  31. package/dist/src/components/Notification/RecordingInProgressNotification.d.ts +5 -0
  32. package/dist/src/components/Notification/SpeakingWhileMutedNotification.d.ts +3 -1
  33. package/dist/src/components/Notification/index.d.ts +1 -0
  34. package/dist/src/components/index.d.ts +2 -0
  35. package/dist/src/core/components/ParticipantView/DefaultParticipantViewUI.d.ts +7 -1
  36. package/dist/src/core/components/ParticipantView/ParticipantActionsContextMenu.d.ts +1 -0
  37. package/dist/src/core/components/ParticipantView/ParticipantViewContext.d.ts +3 -3
  38. package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
  39. package/dist/src/hooks/useFloatingUIPreset.d.ts +4 -1
  40. package/dist/src/translations/index.d.ts +9 -0
  41. package/package.json +7 -9
  42. package/src/components/Button/CompositeButton.tsx +78 -26
  43. package/src/components/Button/IconButton.tsx +22 -21
  44. package/src/components/Button/index.ts +0 -1
  45. package/src/components/CallControls/AcceptCallButton.tsx +1 -0
  46. package/src/components/CallControls/CallControls.tsx +2 -2
  47. package/src/components/CallControls/CallStatsButton.tsx +24 -7
  48. package/src/components/CallControls/CancelCallButton.tsx +102 -3
  49. package/src/components/CallControls/ReactionsButton.tsx +37 -17
  50. package/src/components/CallControls/RecordCallButton.tsx +131 -21
  51. package/src/components/CallControls/ScreenShareButton.tsx +29 -15
  52. package/src/components/CallControls/ToggleAudioButton.tsx +76 -31
  53. package/src/components/CallControls/ToggleAudioOutputButton.tsx +14 -10
  54. package/src/components/CallControls/ToggleVideoButton.tsx +83 -33
  55. package/src/components/CallParticipantsList/CallParticipantListHeader.tsx +9 -6
  56. package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +17 -281
  57. package/src/components/CallParticipantsList/CallParticipantsList.tsx +2 -32
  58. package/src/components/CallRecordingList/CallRecordingList.tsx +24 -6
  59. package/src/components/CallRecordingList/CallRecordingListHeader.tsx +6 -2
  60. package/src/components/CallRecordingList/CallRecordingListItem.tsx +18 -41
  61. package/src/components/CallStats/CallStats.tsx +167 -10
  62. package/src/components/CallStats/CallStatsLatencyChart.tsx +73 -44
  63. package/src/components/DeviceSettings/DeviceSelector.tsx +107 -12
  64. package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +13 -5
  65. package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +10 -4
  66. package/src/components/DeviceSettings/DeviceSettings.tsx +40 -28
  67. package/src/components/DropdownSelect/DropdownSelect.tsx +214 -0
  68. package/src/components/DropdownSelect/index.ts +1 -0
  69. package/src/components/Icon/Icon.tsx +7 -2
  70. package/src/components/Menu/GenericMenu.tsx +25 -3
  71. package/src/components/Menu/MenuToggle.tsx +79 -14
  72. package/src/components/Notification/Notification.tsx +8 -0
  73. package/src/components/Notification/PermissionNotification.tsx +2 -1
  74. package/src/components/Notification/RecordingInProgressNotification.tsx +40 -0
  75. package/src/components/Notification/SpeakingWhileMutedNotification.tsx +9 -1
  76. package/src/components/Notification/index.ts +1 -0
  77. package/src/components/Permissions/PermissionRequests.tsx +9 -21
  78. package/src/components/Search/hooks/useSearch.ts +5 -1
  79. package/src/components/index.ts +2 -0
  80. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +71 -57
  81. package/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx +241 -0
  82. package/src/core/components/ParticipantView/ParticipantView.tsx +2 -2
  83. package/src/core/components/ParticipantView/ParticipantViewContext.tsx +3 -3
  84. package/src/core/components/ParticipantView/index.ts +1 -0
  85. package/src/core/components/Video/BaseVideo.tsx +1 -1
  86. package/src/core/components/Video/DefaultVideoPlaceholder.tsx +19 -5
  87. package/src/hooks/useFloatingUIPreset.ts +3 -2
  88. package/src/hooks/useRequestPermission.ts +2 -1
  89. package/src/translations/en.json +9 -0
  90. package/dist/src/components/Button/CopyToClipboardButton.d.ts +0 -27
  91. package/src/components/Button/CopyToClipboardButton.tsx +0 -129
@@ -1,22 +1,26 @@
1
- import { CompositeButton, IconButton } from '../Button';
2
- import { DeviceSelectorAudioOutput } from '../DeviceSettings';
1
+ import { CompositeButton, IconButtonWithMenuProps } from '../Button';
3
2
  import { useI18n } from '@stream-io/video-react-bindings';
4
- import { ComponentType } from 'react';
3
+ import { Icon } from '../Icon';
5
4
 
6
- export type ToggleAudioOutputButtonProps = {
7
- caption?: string;
8
- Menu?: ComponentType;
9
- };
5
+ export type ToggleAudioOutputButtonProps = Pick<
6
+ IconButtonWithMenuProps,
7
+ 'caption' | 'Menu' | 'menuPlacement'
8
+ >;
10
9
 
11
10
  export const ToggleAudioOutputButton = (
12
11
  props: ToggleAudioOutputButtonProps,
13
12
  ) => {
14
13
  const { t } = useI18n();
15
- const { caption = t('Speakers'), Menu = DeviceSelectorAudioOutput } = props;
14
+ const { caption, Menu } = props;
16
15
 
17
16
  return (
18
- <CompositeButton Menu={Menu} caption={caption}>
19
- <IconButton icon="speaker" />
17
+ <CompositeButton
18
+ Menu={Menu}
19
+ caption={caption}
20
+ title={caption || t('Speakers')}
21
+ data-testid="audio-output-button"
22
+ >
23
+ <Icon icon="speaker" />
20
24
  </CompositeButton>
21
25
  );
22
26
  };
@@ -1,56 +1,82 @@
1
- import { ComponentType } from 'react';
2
1
  import {
3
2
  Restricted,
4
3
  useCallStateHooks,
5
4
  useI18n,
6
5
  } from '@stream-io/video-react-bindings';
7
-
6
+ import clsx from 'clsx';
8
7
  import { OwnCapability } from '@stream-io/video-client';
9
- import { CompositeButton, IconButton } from '../Button/';
8
+ import { CompositeButton, IconButtonWithMenuProps } from '../Button/';
10
9
  import { DeviceSelectorVideo } from '../DeviceSettings';
11
10
  import { PermissionNotification } from '../Notification';
12
11
  import { useRequestPermission } from '../../hooks';
12
+ import { Icon } from '../Icon';
13
13
 
14
- export type ToggleVideoPreviewButtonProps = {
15
- caption?: string;
16
- Menu?: ComponentType;
17
- };
14
+ export type ToggleVideoPreviewButtonProps = Pick<
15
+ IconButtonWithMenuProps,
16
+ 'caption' | 'Menu' | 'menuPlacement'
17
+ >;
18
18
 
19
19
  export const ToggleVideoPreviewButton = (
20
20
  props: ToggleVideoPreviewButtonProps,
21
21
  ) => {
22
+ const { caption, ...restCompositeButtonProps } = props;
22
23
  const { t } = useI18n();
23
- const { caption = t('Video'), Menu = DeviceSelectorVideo } = props;
24
-
25
24
  const { useCameraState } = useCallStateHooks();
26
- const { camera, isMute } = useCameraState();
25
+ const { camera, isMute, hasBrowserPermission } = useCameraState();
27
26
 
28
27
  return (
29
- <CompositeButton Menu={Menu} active={isMute} caption={caption}>
30
- <IconButton
31
- icon={!isMute ? 'camera' : 'camera-off'}
32
- onClick={() => camera.toggle()}
33
- />
28
+ <CompositeButton
29
+ active={isMute}
30
+ caption={caption}
31
+ className={clsx(!hasBrowserPermission && 'str-video__device-unavailable')}
32
+ title={
33
+ !hasBrowserPermission
34
+ ? t('Check your browser video permissions')
35
+ : caption || t('Video')
36
+ }
37
+ variant="secondary"
38
+ data-testid={
39
+ isMute ? 'preview-video-unmute-button' : 'preview-video-mute-button'
40
+ }
41
+ onClick={() => camera.toggle()}
42
+ disabled={!hasBrowserPermission}
43
+ {...restCompositeButtonProps}
44
+ >
45
+ <Icon icon={!isMute ? 'camera' : 'camera-off'} />
46
+ {!hasBrowserPermission && (
47
+ <span
48
+ className="str-video__no-media-permission"
49
+ title={t('Check your browser video permissions')}
50
+ children="!"
51
+ />
52
+ )}
34
53
  </CompositeButton>
35
54
  );
36
55
  };
37
56
 
38
- type ToggleVideoPublishingButtonProps = {
39
- caption?: string;
40
- Menu?: ComponentType;
41
- };
57
+ type ToggleVideoPublishingButtonProps = Pick<
58
+ IconButtonWithMenuProps,
59
+ 'caption' | 'Menu' | 'menuPlacement'
60
+ >;
42
61
 
43
62
  export const ToggleVideoPublishingButton = (
44
63
  props: ToggleVideoPublishingButtonProps,
45
64
  ) => {
46
65
  const { t } = useI18n();
47
- const { caption = t('Video'), Menu = DeviceSelectorVideo } = props;
66
+ const {
67
+ caption,
68
+ Menu = <DeviceSelectorVideo visualType="list" />,
69
+ menuPlacement = 'top',
70
+ ...restCompositeButtonProps
71
+ } = props;
48
72
 
49
73
  const { hasPermission, requestPermission, isAwaitingPermission } =
50
74
  useRequestPermission(OwnCapability.SEND_VIDEO);
51
75
 
52
- const { useCameraState } = useCallStateHooks();
53
- const { camera, isMute } = useCameraState();
76
+ const { useCameraState, useCallSettings } = useCallStateHooks();
77
+ const { camera, isMute, hasBrowserPermission } = useCameraState();
78
+ const callSettings = useCallSettings();
79
+ const isPublishingVideoAllowed = callSettings?.video.enabled;
54
80
 
55
81
  return (
56
82
  <Restricted requiredGrants={[OwnCapability.SEND_VIDEO]}>
@@ -63,17 +89,41 @@ export const ToggleVideoPublishingButton = (
63
89
  )}
64
90
  messageRevoked={t('You can no longer share your video.')}
65
91
  >
66
- <CompositeButton Menu={Menu} active={isMute} caption={caption}>
67
- <IconButton
68
- icon={isMute ? 'camera-off' : 'camera'}
69
- onClick={async () => {
70
- if (!hasPermission) {
71
- await requestPermission();
72
- } else {
73
- await camera.toggle();
74
- }
75
- }}
76
- />
92
+ <CompositeButton
93
+ active={isMute}
94
+ caption={caption}
95
+ variant="secondary"
96
+ title={
97
+ !hasPermission
98
+ ? t('You have no permission to share your video')
99
+ : !hasBrowserPermission
100
+ ? t('Check your browser video permissions')
101
+ : !isPublishingVideoAllowed
102
+ ? t('Video publishing is disabled by the system')
103
+ : caption || t('Video')
104
+ }
105
+ disabled={
106
+ !hasBrowserPermission || !hasPermission || !isPublishingVideoAllowed
107
+ }
108
+ data-testid={isMute ? 'video-unmute-button' : 'video-mute-button'}
109
+ onClick={async () => {
110
+ if (!hasPermission) {
111
+ await requestPermission();
112
+ } else {
113
+ await camera.toggle();
114
+ }
115
+ }}
116
+ Menu={Menu}
117
+ menuPlacement={menuPlacement}
118
+ menuOffset={16}
119
+ {...restCompositeButtonProps}
120
+ >
121
+ <Icon icon={isMute ? 'camera-off' : 'camera'} />
122
+ {(!hasBrowserPermission ||
123
+ !hasPermission ||
124
+ !isPublishingVideoAllowed) && (
125
+ <span className="str-video__no-media-permission">!</span>
126
+ )}
77
127
  </CompositeButton>
78
128
  </PermissionNotification>
79
129
  </Restricted>
@@ -1,7 +1,11 @@
1
1
  import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings';
2
2
 
3
+ import { IconButton } from '../Button';
4
+
3
5
  export type CallParticipantListHeaderProps = {
4
- /** Click event listener function to be invoked in order to dismiss / hide the CallParticipantsList from the UI */
6
+ /**
7
+ * Click event listener function to be invoked to dismiss / hide the CallParticipantsList from the UI.
8
+ */
5
9
  onClose: () => void;
6
10
  };
7
11
 
@@ -18,7 +22,7 @@ export const CallParticipantListHeader = ({
18
22
  <div className="str-video__participant-list-header__title">
19
23
  {t('Participants')}{' '}
20
24
  <span className="str-video__participant-list-header__title-count">
21
- ({participants.length})
25
+ [{participants.length}]
22
26
  </span>
23
27
  {anonymousParticipantCount > 0 && (
24
28
  <span className="str-video__participant-list-header__title-anonymous">
@@ -26,12 +30,11 @@ export const CallParticipantListHeader = ({
26
30
  </span>
27
31
  )}
28
32
  </div>
29
- <button
33
+ <IconButton
30
34
  onClick={onClose}
31
35
  className="str-video__participant-list-header__close-button"
32
- >
33
- <span className="str-video__participant-list-header__close-button--icon" />
34
- </button>
36
+ icon="close"
37
+ />
35
38
  </div>
36
39
  );
37
40
  };
@@ -1,31 +1,15 @@
1
1
  import clsx from 'clsx';
2
- import {
3
- ComponentProps,
4
- ComponentType,
5
- forwardRef,
6
- useEffect,
7
- useState,
8
- } from 'react';
9
- import {
10
- Restricted,
11
- useCall,
12
- useConnectedUser,
13
- useI18n,
14
- } from '@stream-io/video-react-bindings';
15
- import {
16
- OwnCapability,
17
- SfuModels,
18
- StreamVideoParticipant,
19
- } from '@stream-io/video-client';
2
+ import { ComponentProps, ComponentType, forwardRef } from 'react';
3
+ import { useConnectedUser, useI18n } from '@stream-io/video-react-bindings';
4
+ import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client';
20
5
  import { IconButton } from '../Button';
21
- import {
22
- GenericMenu,
23
- GenericMenuButtonItem,
24
- MenuToggle,
25
- ToggleMenuButtonProps,
26
- } from '../Menu';
6
+ import { MenuToggle, ToggleMenuButtonProps } from '../Menu';
27
7
  import { WithTooltip } from '../Tooltip';
28
- import { Icon } from '../Icon';
8
+ import { Avatar } from '../Avatar';
9
+ import {
10
+ ParticipantActionsContextMenu,
11
+ ParticipantViewContext,
12
+ } from '../../core/';
29
13
 
30
14
  type CallParticipantListingItemProps = {
31
15
  /** Participant object be rendered */
@@ -49,6 +33,7 @@ export const CallParticipantListingItem = ({
49
33
 
50
34
  return (
51
35
  <div className="str-video__participant-listing-item">
36
+ <Avatar name={participant.name} imageSrc={participant.image} />
52
37
  <DisplayName participant={participant} />
53
38
  <div className="str-video__participant-listing-item__media-indicator-group">
54
39
  <MediaIndicator
@@ -80,7 +65,11 @@ export const CallParticipantListingItem = ({
80
65
  )}
81
66
 
82
67
  <MenuToggle placement="bottom-end" ToggleButton={ToggleButton}>
83
- <ParticipantActionsContextMenu participant={participant} />
68
+ <ParticipantViewContext.Provider
69
+ value={{ participant, trackType: 'none' }}
70
+ >
71
+ <ParticipantActionsContextMenu />
72
+ </ParticipantViewContext.Provider>
84
73
  </MenuToggle>
85
74
  </div>
86
75
  </div>
@@ -95,7 +84,7 @@ type DisplayNameProps = {
95
84
  /** Participant object that provides the data from which display name can be generated */
96
85
  participant: StreamVideoParticipant;
97
86
  };
98
- // todo: implement display device flag
87
+
99
88
  const DefaultDisplayName = ({ participant }: DisplayNameProps) => {
100
89
  const connectedUser = useConnectedUser();
101
90
  const { t } = useI18n();
@@ -122,260 +111,7 @@ const DefaultDisplayName = ({ participant }: DisplayNameProps) => {
122
111
  };
123
112
 
124
113
  const ToggleButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
125
- (props, ref) => {
114
+ function ToggleButton(props, ref) {
126
115
  return <IconButton enabled={props.menuShown} icon="ellipsis" ref={ref} />;
127
116
  },
128
117
  );
129
-
130
- export const ParticipantActionsContextMenu = ({
131
- participant,
132
- participantViewElement,
133
- videoElement,
134
- }: {
135
- participant: StreamVideoParticipant;
136
- participantViewElement?: HTMLDivElement | null;
137
- videoElement?: HTMLVideoElement | null;
138
- }) => {
139
- const [fullscreenModeOn, setFullscreenModeOn] = useState(
140
- !!document.fullscreenElement,
141
- );
142
- const [pictureInPictureElement, setPictureInPictureElement] = useState(
143
- document.pictureInPictureElement,
144
- );
145
- const call = useCall();
146
- const { t } = useI18n();
147
-
148
- const { pin, publishedTracks, sessionId, userId } = participant;
149
-
150
- const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO);
151
- const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO);
152
- const hasScreenShare = publishedTracks.includes(
153
- SfuModels.TrackType.SCREEN_SHARE,
154
- );
155
- const hasScreenShareAudio = publishedTracks.includes(
156
- SfuModels.TrackType.SCREEN_SHARE_AUDIO,
157
- );
158
-
159
- const blockUser = () => call?.blockUser(userId);
160
- const muteAudio = () => call?.muteUser(userId, 'audio');
161
- const muteVideo = () => call?.muteUser(userId, 'video');
162
- const muteScreenShare = () => call?.muteUser(userId, 'screenshare');
163
- const muteScreenShareAudio = () =>
164
- call?.muteUser(userId, 'screenshare_audio');
165
-
166
- const grantPermission = (permission: string) => () => {
167
- call?.updateUserPermissions({
168
- user_id: userId,
169
- grant_permissions: [permission],
170
- });
171
- };
172
-
173
- const revokePermission = (permission: string) => () => {
174
- call?.updateUserPermissions({
175
- user_id: userId,
176
- revoke_permissions: [permission],
177
- });
178
- };
179
-
180
- const toggleParticipantPinnedAt = () => {
181
- if (pin) {
182
- call?.unpin(sessionId);
183
- } else {
184
- call?.pin(sessionId);
185
- }
186
- };
187
-
188
- const pinForEveryone = () => {
189
- call
190
- ?.pinForEveryone({
191
- user_id: userId,
192
- session_id: sessionId,
193
- })
194
- .catch((err) => {
195
- console.error(`Failed to pin participant ${userId}`, err);
196
- });
197
- };
198
-
199
- const unpinForEveryone = () => {
200
- call
201
- ?.unpinForEveryone({
202
- user_id: userId,
203
- session_id: sessionId,
204
- })
205
- .catch((err) => {
206
- console.error(`Failed to unpin participant ${userId}`, err);
207
- });
208
- };
209
-
210
- const toggleFullscreenMode = () => {
211
- if (!fullscreenModeOn) {
212
- return participantViewElement
213
- ?.requestFullscreen()
214
- .then(() => setFullscreenModeOn(true))
215
- .catch(console.error);
216
- }
217
-
218
- document
219
- .exitFullscreen()
220
- .catch(console.error)
221
- .finally(() => setFullscreenModeOn(false));
222
- };
223
-
224
- useEffect(() => {
225
- // handles the case when fullscreen mode is toggled externally,
226
- // e.g., by pressing ESC key or some other keyboard shortcut
227
- const handleFullscreenChange = () => {
228
- setFullscreenModeOn(!!document.fullscreenElement);
229
- };
230
- document.addEventListener('fullscreenchange', handleFullscreenChange);
231
- return () => {
232
- document.removeEventListener('fullscreenchange', handleFullscreenChange);
233
- };
234
- }, []);
235
-
236
- useEffect(() => {
237
- if (!videoElement) return;
238
-
239
- const handlePictureInPicture = () => {
240
- setPictureInPictureElement(document.pictureInPictureElement);
241
- };
242
-
243
- videoElement.addEventListener(
244
- 'enterpictureinpicture',
245
- handlePictureInPicture,
246
- );
247
- videoElement.addEventListener(
248
- 'leavepictureinpicture',
249
- handlePictureInPicture,
250
- );
251
-
252
- return () => {
253
- videoElement.removeEventListener(
254
- 'enterpictureinpicture',
255
- handlePictureInPicture,
256
- );
257
- videoElement.removeEventListener(
258
- 'leavepictureinpicture',
259
- handlePictureInPicture,
260
- );
261
- };
262
- }, [videoElement]);
263
-
264
- const togglePictureInPicture = () => {
265
- if (videoElement && pictureInPictureElement !== videoElement) {
266
- return videoElement
267
- .requestPictureInPicture()
268
- .catch(console.error) as Promise<void>;
269
- }
270
-
271
- document.exitPictureInPicture().catch(console.error);
272
- };
273
-
274
- return (
275
- <GenericMenu>
276
- <GenericMenuButtonItem
277
- onClick={toggleParticipantPinnedAt}
278
- disabled={pin && !pin.isLocalPin}
279
- >
280
- <Icon icon="pin" />
281
- {pin ? t('Unpin') : t('Pin')}
282
- </GenericMenuButtonItem>
283
- <Restricted requiredGrants={[OwnCapability.PIN_FOR_EVERYONE]}>
284
- <GenericMenuButtonItem
285
- onClick={pinForEveryone}
286
- disabled={pin && !pin.isLocalPin}
287
- >
288
- <Icon icon="pin" />
289
- {t('Pin for everyone')}
290
- </GenericMenuButtonItem>
291
- <GenericMenuButtonItem
292
- onClick={unpinForEveryone}
293
- disabled={!pin || pin.isLocalPin}
294
- >
295
- <Icon icon="pin" />
296
- {t('Unpin for everyone')}
297
- </GenericMenuButtonItem>
298
- </Restricted>
299
- <Restricted requiredGrants={[OwnCapability.BLOCK_USERS]}>
300
- <GenericMenuButtonItem onClick={blockUser}>
301
- <Icon icon="not-allowed" />
302
- {t('Block')}
303
- </GenericMenuButtonItem>
304
- </Restricted>
305
- <Restricted requiredGrants={[OwnCapability.MUTE_USERS]}>
306
- <GenericMenuButtonItem disabled={!hasVideo} onClick={muteVideo}>
307
- <Icon icon="camera-off-outline" />
308
- {t('Turn off video')}
309
- </GenericMenuButtonItem>
310
- <GenericMenuButtonItem
311
- disabled={!hasScreenShare}
312
- onClick={muteScreenShare}
313
- >
314
- <Icon icon="screen-share-off" />
315
- {t('Turn off screen share')}
316
- </GenericMenuButtonItem>
317
- <GenericMenuButtonItem disabled={!hasAudio} onClick={muteAudio}>
318
- <Icon icon="no-audio" />
319
- {t('Mute audio')}
320
- </GenericMenuButtonItem>
321
- <GenericMenuButtonItem
322
- disabled={!hasScreenShareAudio}
323
- onClick={muteScreenShareAudio}
324
- >
325
- <Icon icon="no-audio" />
326
- {t('Mute screen share audio')}
327
- </GenericMenuButtonItem>
328
- </Restricted>
329
- {participantViewElement && (
330
- <GenericMenuButtonItem onClick={toggleFullscreenMode}>
331
- {t('{{ direction }} fullscreen', {
332
- direction: fullscreenModeOn ? t('Leave') : t('Enter'),
333
- })}
334
- </GenericMenuButtonItem>
335
- )}
336
- {videoElement && document.pictureInPictureEnabled && (
337
- <GenericMenuButtonItem onClick={togglePictureInPicture}>
338
- {t('{{ direction }} picture-in-picture', {
339
- direction:
340
- pictureInPictureElement === videoElement
341
- ? t('Leave')
342
- : t('Enter'),
343
- })}
344
- </GenericMenuButtonItem>
345
- )}
346
- <Restricted requiredGrants={[OwnCapability.UPDATE_CALL_PERMISSIONS]}>
347
- <GenericMenuButtonItem
348
- onClick={grantPermission(OwnCapability.SEND_AUDIO)}
349
- >
350
- {t('Allow audio')}
351
- </GenericMenuButtonItem>
352
- <GenericMenuButtonItem
353
- onClick={grantPermission(OwnCapability.SEND_VIDEO)}
354
- >
355
- {t('Allow video')}
356
- </GenericMenuButtonItem>
357
- <GenericMenuButtonItem
358
- onClick={grantPermission(OwnCapability.SCREENSHARE)}
359
- >
360
- {t('Allow screen sharing')}
361
- </GenericMenuButtonItem>
362
-
363
- <GenericMenuButtonItem
364
- onClick={revokePermission(OwnCapability.SEND_AUDIO)}
365
- >
366
- {t('Disable audio')}
367
- </GenericMenuButtonItem>
368
- <GenericMenuButtonItem
369
- onClick={revokePermission(OwnCapability.SEND_VIDEO)}
370
- >
371
- {t('Disable video')}
372
- </GenericMenuButtonItem>
373
- <GenericMenuButtonItem
374
- onClick={revokePermission(OwnCapability.SCREENSHARE)}
375
- >
376
- {t('Disable screen sharing')}
377
- </GenericMenuButtonItem>
378
- </Restricted>
379
- </GenericMenu>
380
- );
381
- };
@@ -1,7 +1,5 @@
1
1
  import {
2
- ComponentProps,
3
2
  Dispatch,
4
- ForwardedRef,
5
3
  forwardRef,
6
4
  SetStateAction,
7
5
  useCallback,
@@ -17,14 +15,9 @@ import {
17
15
  OwnCapability,
18
16
  StreamVideoParticipant,
19
17
  } from '@stream-io/video-client';
20
- import clsx from 'clsx';
21
18
 
22
19
  import { BlockedUserListing } from './BlockedUserListing';
23
- import {
24
- CopyToClipboardButtonWithPopup,
25
- IconButton,
26
- TextButton,
27
- } from '../Button';
20
+ import { IconButton, TextButton } from '../Button';
28
21
  import { CallParticipantListHeader } from './CallParticipantListHeader';
29
22
  import { CallParticipantListing } from './CallParticipantListing';
30
23
  import { EmptyParticipantSearchList } from './EmptyParticipantSearchList';
@@ -97,12 +90,6 @@ export const CallParticipantsList = ({
97
90
  />
98
91
  )}
99
92
  </div>
100
- <div className="str-video__participant-list__footer">
101
- <CopyToClipboardButtonWithPopup
102
- Button={InviteLinkButton}
103
- copyValue={typeof window !== 'undefined' ? window.location.href : ''}
104
- />
105
- </div>
106
93
  </div>
107
94
  );
108
95
  };
@@ -123,7 +110,6 @@ const CallParticipantListContentHeader = ({
123
110
  return (
124
111
  <div className="str-video__participant-list__content-header">
125
112
  <div className="str-video__participant-list__content-header-title">
126
- <span>{UserListTypes[userListType]}</span>
127
113
  {userListType === 'active' && (
128
114
  <Restricted
129
115
  requiredGrants={[OwnCapability.MUTE_USERS]}
@@ -234,23 +220,7 @@ const BlockedUsersSearchResults = ({
234
220
  };
235
221
 
236
222
  const ToggleButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
237
- (props, ref) => {
223
+ function ToggleButton(props, ref) {
238
224
  return <IconButton enabled={props.menuShown} icon="filter" ref={ref} />;
239
225
  },
240
226
  );
241
-
242
- const InviteLinkButton = forwardRef(
243
- (
244
- { className, ...props }: ComponentProps<'button'>,
245
- ref: ForwardedRef<HTMLButtonElement>,
246
- ) => (
247
- <button
248
- {...props}
249
- className={clsx('str-video__invite-link-button', className)}
250
- ref={ref}
251
- >
252
- <div className="str-video__invite-participant-icon" />
253
- <div className="str-video__invite-link-button__text">Invite Link</div>
254
- </button>
255
- ),
256
- );
@@ -51,12 +51,30 @@ export const CallRecordingList = ({
51
51
  {loading ? (
52
52
  <LoadingCallRecordingList callRecordings={callRecordings} />
53
53
  ) : callRecordings.length ? (
54
- callRecordings.map((recording) => (
55
- <CallRecordingListItem
56
- recording={recording}
57
- key={recording.filename}
58
- />
59
- ))
54
+ <>
55
+ <ul className="str-video__call-recording-list__list">
56
+ <li className="str-video__call-recording-list__item">
57
+ <div className="str-video__call-recording-list__filename">
58
+ Name
59
+ </div>
60
+ <div className="str-video__call-recording-list__time">
61
+ Start time
62
+ </div>
63
+ <div className="str-video__call-recording-list__time">
64
+ End time
65
+ </div>
66
+ <div className="str-video__call-recording-list__download"></div>
67
+ </li>
68
+ </ul>
69
+ <ul className="str-video__call-recording-list__list">
70
+ {callRecordings.map((recording) => (
71
+ <CallRecordingListItem
72
+ recording={recording}
73
+ key={recording.filename}
74
+ />
75
+ ))}
76
+ </ul>
77
+ </>
60
78
  ) : (
61
79
  <EmptyCallRecordingList />
62
80
  )}