@100mslive/roomkit-react 0.1.7-alpha.0 → 0.1.8-alpha.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 (64) hide show
  1. package/dist/AudioLevel/AudioLevel.d.ts +5 -8
  2. package/dist/AudioLevel/index.d.ts +2 -1
  3. package/dist/AudioLevel/useBorderAudioLevel.d.ts +8 -0
  4. package/dist/{HLSView-F5BDZVT2.js → HLSView-IQRPLYNH.js} +8 -6
  5. package/dist/{HLSView-F5BDZVT2.js.map → HLSView-IQRPLYNH.js.map} +2 -2
  6. package/dist/Prebuilt/components/Chip.d.ts +12 -0
  7. package/dist/Prebuilt/components/PrebuiltDialogPortal.d.ts +4 -0
  8. package/dist/Prebuilt/components/RoleChangeRequest/RequestPrompt.d.ts +9 -0
  9. package/dist/VideoTile/StyledVideoTile.d.ts +445 -3
  10. package/dist/{VirtualBackground-THDRYDRA.js → VirtualBackground-GP4ATXD3.js} +3 -3
  11. package/dist/{chunk-JSH7SKEH.js → chunk-2H5NIZB7.js} +2 -2
  12. package/dist/{chunk-CDYRVICT.js → chunk-GLYGPYNS.js} +574 -1196
  13. package/dist/chunk-GLYGPYNS.js.map +7 -0
  14. package/dist/{chunk-U3G743OY.js → chunk-Z3O2WGWV.js} +2 -2
  15. package/dist/{chunk-U3G743OY.js.map → chunk-Z3O2WGWV.js.map} +1 -1
  16. package/dist/{conference-6IVZHILI.js → conference-JD35TNH4.js} +1545 -840
  17. package/dist/conference-JD35TNH4.js.map +7 -0
  18. package/dist/index.cjs.js +5975 -5849
  19. package/dist/index.cjs.js.map +4 -4
  20. package/dist/index.js +4 -2
  21. package/dist/meta.cjs.json +1633 -1399
  22. package/dist/meta.esbuild.json +1689 -1454
  23. package/package.json +6 -6
  24. package/src/AudioLevel/AudioLevel.tsx +79 -30
  25. package/src/AudioLevel/audio-level.png +0 -0
  26. package/src/AudioLevel/index.ts +2 -1
  27. package/src/AudioLevel/useBorderAudioLevel.tsx +34 -0
  28. package/src/Input/Input.tsx +1 -1
  29. package/src/Prebuilt/App.tsx +1 -0
  30. package/src/Prebuilt/components/Chat/ChatBody.jsx +125 -106
  31. package/src/Prebuilt/components/{Chip.jsx → Chip.tsx} +13 -2
  32. package/src/Prebuilt/components/Footer/ParticipantList.jsx +24 -13
  33. package/src/Prebuilt/components/Footer/RoleAccordion.tsx +43 -3
  34. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +41 -46
  35. package/src/Prebuilt/components/Leave/MwebLeaveRoom.tsx +23 -35
  36. package/src/Prebuilt/components/MoreSettings/ChangeNameModal.jsx +3 -2
  37. package/src/Prebuilt/components/MoreSettings/EmbedUrl.jsx +3 -2
  38. package/src/Prebuilt/components/MwebLandscapePrompt.jsx +58 -0
  39. package/src/Prebuilt/components/Notifications/HLSFailureModal.jsx +3 -2
  40. package/src/Prebuilt/components/Notifications/PermissionErrorModal.jsx +3 -2
  41. package/src/Prebuilt/components/PrebuiltDialogPortal.tsx +6 -0
  42. package/src/Prebuilt/components/Preview/PreviewJoin.tsx +9 -6
  43. package/src/Prebuilt/components/RaiseHand.jsx +4 -11
  44. package/src/Prebuilt/components/RoleChangeModal.jsx +3 -2
  45. package/src/Prebuilt/components/RoleChangeRequest/RequestPrompt.tsx +67 -0
  46. package/src/Prebuilt/components/{RoleChangeRequestModal.tsx → RoleChangeRequest/RoleChangeRequestModal.tsx} +18 -50
  47. package/src/Prebuilt/components/Settings/SettingsModal.jsx +3 -2
  48. package/src/Prebuilt/components/Settings/StartRecording.jsx +3 -2
  49. package/src/Prebuilt/components/StatsForNerds.jsx +3 -2
  50. package/src/Prebuilt/components/VideoTile.jsx +34 -75
  51. package/src/Prebuilt/components/conference.jsx +1 -1
  52. package/src/Prebuilt/components/hooks/useMetadata.jsx +2 -1
  53. package/src/Prebuilt/components/pdfAnnotator/pdfFileOptions.jsx +3 -2
  54. package/src/Prebuilt/components/pdfAnnotator/shareScreenOptions.jsx +4 -29
  55. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +3 -2
  56. package/src/Prebuilt/layouts/HLSView.jsx +4 -2
  57. package/src/Prebuilt/layouts/SidePane.tsx +0 -1
  58. package/src/Prebuilt/primitives/DialogContent.jsx +5 -4
  59. package/src/VideoTile/StyledVideoTile.tsx +10 -14
  60. package/dist/chunk-CDYRVICT.js.map +0 -7
  61. package/dist/conference-6IVZHILI.js.map +0 -7
  62. /package/dist/Prebuilt/components/{RoleChangeRequestModal.d.ts → RoleChangeRequest/RoleChangeRequestModal.d.ts} +0 -0
  63. /package/dist/{VirtualBackground-THDRYDRA.js.map → VirtualBackground-GP4ATXD3.js.map} +0 -0
  64. /package/dist/{chunk-JSH7SKEH.js.map → chunk-2H5NIZB7.js.map} +0 -0
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.1.7-alpha.0",
13
+ "version": "0.1.8-alpha.0",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "files": [
@@ -76,10 +76,10 @@
76
76
  "react": ">=17.0.2 <19.0.0"
77
77
  },
78
78
  "dependencies": {
79
- "@100mslive/hls-player": "0.1.16-alpha.0",
80
- "@100mslive/hms-virtual-background": "1.11.16-alpha.0",
81
- "@100mslive/react-icons": "0.8.16-alpha.0",
82
- "@100mslive/react-sdk": "0.8.16-alpha.0",
79
+ "@100mslive/hls-player": "0.1.17-alpha.0",
80
+ "@100mslive/hms-virtual-background": "1.11.17-alpha.0",
81
+ "@100mslive/react-icons": "0.8.17-alpha.0",
82
+ "@100mslive/react-sdk": "0.8.17-alpha.0",
83
83
  "@100mslive/types-prebuilt": "0.12.0",
84
84
  "@emoji-mart/data": "^1.0.6",
85
85
  "@emoji-mart/react": "^1.0.1",
@@ -115,5 +115,5 @@
115
115
  "uuid": "^8.3.2",
116
116
  "worker-timers": "^7.0.40"
117
117
  },
118
- "gitHead": "02a796684045c2468c14db4cb894c56a17c6c2ba"
118
+ "gitHead": "d6e072de081b508a297ebc1684a33a8e9db048ef"
119
119
  }
@@ -1,34 +1,83 @@
1
- import { useCallback, useRef } from 'react';
2
- import { HMSTrackID } from '@100mslive/hms-video-store';
3
- import { useAudioLevelStyles } from '@100mslive/react-sdk';
4
- import { useTheme } from '../Theme';
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { selectTrackAudioByID, useHMSVanillaStore } from '@100mslive/react-sdk';
3
+ import { Box, Flex } from '../Layout';
4
+ import { keyframes } from '../Theme';
5
+ //@ts-ignore
6
+ import bg from './audio-level.png';
5
7
 
6
- /**
7
- * pass in a track id and get a ref. That ref can be attached to an element which will have border
8
- * as per audio level post that.
9
- */
10
- export function useBorderAudioLevel(audioTrackId?: HMSTrackID) {
11
- const { theme } = useTheme();
12
- const color = theme.colors.primary_default.value;
13
- const getStyle = useCallback(
14
- (level: number) => {
15
- const style: Record<string, string> = {
16
- transition: 'outline 0.4s ease-in-out',
17
- };
18
- style['outline'] = level ? `${sigmoid(level) * 4}px solid ${color}` : '0px solid transparent';
19
- return style;
20
- },
21
- [color],
8
+ // keep the calculated values before hand to avoid recalcuation everytime
9
+ const positionValues = new Array(101).fill(0).reduce((acc, _, index) => {
10
+ acc[index] = Math.round((index / 100) * 4) / 4; // convert to 0.25 multiples
11
+ return acc;
12
+ }, {});
13
+
14
+ const barAnimation = keyframes({
15
+ from: {
16
+ maskSize: '4em .8em',
17
+ '-webkit-mask-position-y': '.1em',
18
+ maskPosition: 'initial .1em',
19
+ },
20
+
21
+ '50%': {
22
+ maskSize: '4em 1em',
23
+ '-webkit-mask-position-y': 0,
24
+ maskPosition: 'initial 0',
25
+ },
26
+
27
+ to: {
28
+ maskSize: '4em .8em',
29
+ '-webkit-mask-position-y': '.1em',
30
+ maskPosition: 'initial 0',
31
+ },
32
+ });
33
+
34
+ const AudioBar = () => {
35
+ return (
36
+ <Box
37
+ css={{
38
+ width: '.25em',
39
+ height: '1em',
40
+ maskImage: `url(${bg})`,
41
+ '-webkit-mask-repeat': 'no-repeat',
42
+ backgroundColor: '$on_primary_high',
43
+ maskSize: '4em 1em',
44
+ }}
45
+ />
22
46
  );
23
- const ref = useRef(null);
24
- useAudioLevelStyles({
25
- trackId: audioTrackId,
26
- getStyle,
27
- ref,
28
- });
29
- return ref;
30
- }
47
+ };
48
+
49
+ export const AudioLevel = ({ trackId, size }: { trackId?: string; size?: 'small' | 'medium' }) => {
50
+ const ref = useRef<HTMLDivElement | null>(null);
51
+ const vanillaStore = useHMSVanillaStore();
31
52
 
32
- export const sigmoid = (z: number) => {
33
- return 1 / (1 + Math.exp(-z));
53
+ useEffect(() => {
54
+ const unsubscribe = vanillaStore.subscribe(audioLevel => {
55
+ if (ref.current) {
56
+ let index = 0;
57
+ //@ts-ignore
58
+ for (const child of ref.current.children) {
59
+ const positionX = `-${positionValues[audioLevel] * (index === 1 ? 2.5 : 1.25)}em`;
60
+ child.style['-webkit-mask-position-x'] = positionX;
61
+ child.style['mask-position'] = `${positionX} 0`;
62
+ child.style['animation'] =
63
+ positionValues[audioLevel] > 0 ? `${barAnimation} 0.6s steps(3,jump-none) 0s infinite` : 'none';
64
+ index++;
65
+ }
66
+ }
67
+ }, selectTrackAudioByID(trackId));
68
+ return unsubscribe;
69
+ }, [vanillaStore, trackId]);
70
+ return (
71
+ <Flex
72
+ ref={ref}
73
+ css={{
74
+ fontSize: size === 'small' ? '0.75rem' : '1rem',
75
+ gap: size === 'small' ? '$1' : '$2',
76
+ }}
77
+ >
78
+ <AudioBar />
79
+ <AudioBar />
80
+ <AudioBar />
81
+ </Flex>
82
+ );
34
83
  };
Binary file
@@ -1 +1,2 @@
1
- export { useBorderAudioLevel } from './AudioLevel';
1
+ export { useBorderAudioLevel } from './useBorderAudioLevel';
2
+ export { AudioLevel } from './AudioLevel';
@@ -0,0 +1,34 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { HMSTrackID } from '@100mslive/hms-video-store';
3
+ import { useAudioLevelStyles } from '@100mslive/react-sdk';
4
+ import { useTheme } from '../Theme';
5
+
6
+ /**
7
+ * pass in a track id and get a ref. That ref can be attached to an element which will have border
8
+ * as per audio level post that.
9
+ */
10
+ export function useBorderAudioLevel(audioTrackId?: HMSTrackID) {
11
+ const { theme } = useTheme();
12
+ const color = theme.colors.primary_default.value;
13
+ const getStyle = useCallback(
14
+ (level: number) => {
15
+ const style: Record<string, string> = {
16
+ transition: 'outline 0.4s ease-in-out',
17
+ };
18
+ style['outline'] = level ? `${sigmoid(level) * 4}px solid ${color}` : '0px solid transparent';
19
+ return style;
20
+ },
21
+ [color],
22
+ );
23
+ const ref = useRef(null);
24
+ useAudioLevelStyles({
25
+ trackId: audioTrackId,
26
+ getStyle,
27
+ ref,
28
+ });
29
+ return ref;
30
+ }
31
+
32
+ export const sigmoid = (z: number) => {
33
+ return 1 / (1 + Math.exp(-z));
34
+ };
@@ -20,7 +20,7 @@ export const Input = styled('input', {
20
20
  border: '1px solid transparent',
21
21
  },
22
22
  '&::placeholder': {
23
- color: '$on_surface_low',
23
+ color: '$on_surface_medium',
24
24
  },
25
25
  variants: {
26
26
  alert_error_default: {
@@ -218,6 +218,7 @@ export const HMSPrebuilt = React.forwardRef<HMSPrebuiltRefType, HMSPrebuiltProps
218
218
  <AppData appDetails={metadata} tokenEndpoint={tokenByRoomIdRoleEndpoint} />
219
219
  <Init />
220
220
  <Box
221
+ id="prebuilt-container"
221
222
  css={{
222
223
  bg: '$background_dim',
223
224
  size: '100%',
@@ -24,6 +24,7 @@ import { Tooltip } from '../../../Tooltip';
24
24
  import emptyChat from '../../images/empty-chat.svg';
25
25
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
26
26
  import { useSetPinnedMessage } from '../hooks/useSetPinnedMessage';
27
+ import { useUnreadCount } from './useUnreadCount';
27
28
 
28
29
  const formatTime = date => {
29
30
  if (!(date instanceof Date)) {
@@ -192,119 +193,127 @@ const SenderName = styled(Text, {
192
193
  fontWeight: '$semiBold',
193
194
  });
194
195
 
195
- const ChatMessage = React.memo(({ index, style = {}, message, setRowHeight, onPin }) => {
196
- const { ref, inView } = useInView({ threshold: 0.5, triggerOnce: true });
197
- const rowRef = useRef(null);
198
- useEffect(() => {
199
- if (rowRef.current) {
200
- setRowHeight(index, rowRef.current.clientHeight);
201
- }
202
- }, [index, setRowHeight]);
203
- const isMobile = useMedia(cssConfig.media.md);
204
- const { elements } = useRoomLayoutConferencingScreen();
205
- const isOverlay = elements?.chat?.is_overlay && isMobile;
206
- const hmsActions = useHMSActions();
207
- const localPeerId = useHMSStore(selectLocalPeerID);
208
- const permissions = useHMSStore(selectPermissions);
209
- const messageType = getMessageType({
210
- roles: message.recipientRoles,
211
- receiver: message.recipientPeer,
212
- });
213
- // show pin action only if peer has remove others permission and the message is of broadcast type
214
- const showPinAction = permissions.removeOthers && !messageType && elements?.chat?.allow_pinning_messages;
196
+ const ChatMessage = React.memo(
197
+ ({ index, style = {}, message, setRowHeight, isLast = false, unreadCount = 0, scrollToBottom, onPin }) => {
198
+ const { ref, inView } = useInView({ threshold: 0.5, triggerOnce: true });
199
+ const rowRef = useRef(null);
200
+ useEffect(() => {
201
+ if (rowRef.current) {
202
+ setRowHeight(index, rowRef.current.clientHeight);
203
+ }
204
+ }, [index, setRowHeight]);
205
+ const isMobile = useMedia(cssConfig.media.md);
206
+ const { elements } = useRoomLayoutConferencingScreen();
207
+ const isOverlay = elements?.chat?.is_overlay && isMobile;
208
+ const hmsActions = useHMSActions();
209
+ const localPeerId = useHMSStore(selectLocalPeerID);
210
+ const permissions = useHMSStore(selectPermissions);
211
+ const messageType = getMessageType({
212
+ roles: message.recipientRoles,
213
+ receiver: message.recipientPeer,
214
+ });
215
+ // show pin action only if peer has remove others permission and the message is of broadcast type
216
+ const showPinAction = permissions.removeOthers && !messageType && elements?.chat?.allow_pinning_messages;
215
217
 
216
- useEffect(() => {
217
- if (message.id && !message.read && inView) {
218
- hmsActions.setMessageRead(true, message.id);
219
- }
220
- }, [message.read, hmsActions, inView, message.id]);
218
+ useEffect(() => {
219
+ if (message.id && !message.read && inView) {
220
+ hmsActions.setMessageRead(true, message.id);
221
+ }
222
+ }, [message.read, hmsActions, inView, message.id]);
221
223
 
222
- return (
223
- <Box
224
- ref={ref}
225
- as="div"
226
- css={{ mb: '$10', pr: '$10', mt: '$8', '&:hover .chat_actions': { opacity: 1 } }}
227
- style={style}
228
- >
229
- <Flex
230
- ref={rowRef}
231
- align="center"
232
- css={{
233
- flexWrap: 'wrap',
234
- // Theme independent color, token should not be used for transparent chat
235
- bg: messageType ? (isOverlay ? 'rgba(0, 0, 0, 0.64)' : '$surface_default') : undefined,
236
- r: messageType ? '$1' : undefined,
237
- px: messageType ? '$4' : '$2',
238
- py: messageType ? '$4' : 0,
239
- userSelect: 'none',
240
- }}
241
- key={message.time}
242
- data-testid="chat_msg"
224
+ useEffect(() => {
225
+ if (isLast && inView && unreadCount >= 1) {
226
+ scrollToBottom(1);
227
+ }
228
+ }, [inView, isLast, scrollToBottom, unreadCount]);
229
+
230
+ return (
231
+ <Box
232
+ ref={ref}
233
+ as="div"
234
+ css={{ mb: '$10', pr: '$10', mt: '$8', '&:hover .chat_actions': { opacity: 1 } }}
235
+ style={style}
243
236
  >
244
- <Text
237
+ <Flex
238
+ ref={rowRef}
239
+ align="center"
245
240
  css={{
246
- color: isOverlay ? '#FFF' : '$on_surface_high',
247
- fontWeight: '$semiBold',
248
- display: 'inline-flex',
249
- alignItems: 'center',
250
- justifyContent: 'space-between',
251
- width: '100%',
241
+ flexWrap: 'wrap',
242
+ // Theme independent color, token should not be used for transparent chat
243
+ bg: messageType ? (isOverlay ? 'rgba(0, 0, 0, 0.64)' : '$surface_default') : undefined,
244
+ r: messageType ? '$1' : undefined,
245
+ px: messageType ? '$4' : '$2',
246
+ py: messageType ? '$4' : 0,
247
+ userSelect: 'none',
252
248
  }}
253
- as="div"
249
+ key={message.time}
250
+ data-testid="chat_msg"
254
251
  >
255
- <Flex align="baseline">
256
- {message.senderName === 'You' || !message.senderName ? (
257
- <SenderName as="span" variant="sm" css={{ color: isOverlay ? '#FFF' : '$on_surface_high' }}>
258
- {message.senderName || 'Anonymous'}
259
- </SenderName>
260
- ) : (
261
- <Tooltip title={message.senderName} side="top" align="start">
252
+ <Text
253
+ css={{
254
+ color: isOverlay ? '#FFF' : '$on_surface_high',
255
+ fontWeight: '$semiBold',
256
+ display: 'inline-flex',
257
+ alignItems: 'center',
258
+ justifyContent: 'space-between',
259
+ width: '100%',
260
+ }}
261
+ as="div"
262
+ >
263
+ <Flex align="baseline">
264
+ {message.senderName === 'You' || !message.senderName ? (
262
265
  <SenderName as="span" variant="sm" css={{ color: isOverlay ? '#FFF' : '$on_surface_high' }}>
263
- {message.senderName}
266
+ {message.senderName || 'Anonymous'}
264
267
  </SenderName>
265
- </Tooltip>
266
- )}
267
- {!isOverlay ? (
268
- <Text
269
- as="span"
270
- variant="xs"
271
- css={{
272
- ml: '$4',
273
- color: '$on_surface_medium',
274
- flexShrink: 0,
275
- }}
276
- >
277
- {formatTime(message.time)}
278
- </Text>
279
- ) : null}
280
- </Flex>
281
- <MessageType
282
- hasCurrentUserSent={message.sender === localPeerId}
283
- receiver={message.recipientPeer}
284
- roles={message.recipientRoles}
285
- />
286
- {!isOverlay ? <ChatActions onPin={onPin} showPinAction={showPinAction} /> : null}
287
- </Text>
288
- <Text
289
- variant="sm"
290
- css={{
291
- w: '100%',
292
- mt: '$2',
293
- wordBreak: 'break-word',
294
- whiteSpace: 'pre-wrap',
295
- userSelect: 'all',
296
- color: isOverlay ? '#FFF' : '$on_surface_high',
297
- }}
298
- onClick={e => e.stopPropagation()}
299
- >
300
- <AnnotisedMessage message={message.message} />
301
- </Text>
302
- </Flex>
303
- </Box>
304
- );
305
- });
268
+ ) : (
269
+ <Tooltip title={message.senderName} side="top" align="start">
270
+ <SenderName as="span" variant="sm" css={{ color: isOverlay ? '#FFF' : '$on_surface_high' }}>
271
+ {message.senderName}
272
+ </SenderName>
273
+ </Tooltip>
274
+ )}
275
+ {!isOverlay ? (
276
+ <Text
277
+ as="span"
278
+ variant="xs"
279
+ css={{
280
+ ml: '$4',
281
+ color: '$on_surface_medium',
282
+ flexShrink: 0,
283
+ }}
284
+ >
285
+ {formatTime(message.time)}
286
+ </Text>
287
+ ) : null}
288
+ </Flex>
289
+ <MessageType
290
+ hasCurrentUserSent={message.sender === localPeerId}
291
+ receiver={message.recipientPeer}
292
+ roles={message.recipientRoles}
293
+ />
294
+ {!isOverlay ? <ChatActions onPin={onPin} showPinAction={showPinAction} /> : null}
295
+ </Text>
296
+ <Text
297
+ variant="sm"
298
+ css={{
299
+ w: '100%',
300
+ mt: '$2',
301
+ wordBreak: 'break-word',
302
+ whiteSpace: 'pre-wrap',
303
+ userSelect: 'all',
304
+ color: isOverlay ? '#FFF' : '$on_surface_high',
305
+ }}
306
+ onClick={e => e.stopPropagation()}
307
+ >
308
+ <AnnotisedMessage message={message.message} />
309
+ </Text>
310
+ </Flex>
311
+ </Box>
312
+ );
313
+ },
314
+ );
306
315
  const ChatList = React.forwardRef(
307
- ({ width, height, setRowHeight, getRowHeight, messages, scrollToBottom }, listRef) => {
316
+ ({ width, height, setRowHeight, getRowHeight, messages, unreadCount = 0, scrollToBottom }, listRef) => {
308
317
  const { setPinnedMessage } = useSetPinnedMessage();
309
318
  useLayoutEffect(() => {
310
319
  if (listRef.current && listRef.current.scrollToItem) {
@@ -331,6 +340,9 @@ const ChatList = React.forwardRef(
331
340
  key={messages[index].id}
332
341
  message={messages[index]}
333
342
  setRowHeight={setRowHeight}
343
+ unreadCount={unreadCount}
344
+ isLast={index >= messages.length - 2}
345
+ scrollToBottom={scrollToBottom}
334
346
  onPin={() => setPinnedMessage(messages[index])}
335
347
  />
336
348
  )}
@@ -338,7 +350,7 @@ const ChatList = React.forwardRef(
338
350
  );
339
351
  },
340
352
  );
341
- const VirtualizedChatMessages = React.forwardRef(({ messages, scrollToBottom }, listRef) => {
353
+ const VirtualizedChatMessages = React.forwardRef(({ messages, unreadCount = 0, scrollToBottom }, listRef) => {
342
354
  const rowHeights = useRef({});
343
355
 
344
356
  function getRowHeight(index) {
@@ -377,6 +389,7 @@ const VirtualizedChatMessages = React.forwardRef(({ messages, scrollToBottom },
377
389
  getRowHeight={getRowHeight}
378
390
  scrollToBottom={scrollToBottom}
379
391
  ref={listRef}
392
+ unreadCount={unreadCount}
380
393
  />
381
394
  )}
382
395
  </AutoSizer>
@@ -394,6 +407,7 @@ export const ChatBody = React.forwardRef(({ role, peerId, scrollToBottom }, list
394
407
  messages = useMemo(() => messages?.filter(message => message.type === 'chat') || [], [messages]);
395
408
  const isMobile = useMedia(cssConfig.media.md);
396
409
  const { elements } = useRoomLayoutConferencingScreen();
410
+ const unreadCount = useUnreadCount({ role, peerId });
397
411
 
398
412
  if (messages.length === 0 && !(isMobile && elements?.chat?.is_overlay)) {
399
413
  return (
@@ -425,7 +439,12 @@ export const ChatBody = React.forwardRef(({ role, peerId, scrollToBottom }, list
425
439
 
426
440
  return (
427
441
  <Fragment>
428
- <VirtualizedChatMessages messages={messages} scrollToBottom={scrollToBottom} ref={listRef} />
442
+ <VirtualizedChatMessages
443
+ messages={messages}
444
+ scrollToBottom={scrollToBottom}
445
+ unreadCount={unreadCount}
446
+ ref={listRef}
447
+ />
429
448
  </Fragment>
430
449
  );
431
450
  });
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Flex } from '../../Layout';
3
3
  import { Text } from '../../Text';
4
+ import { CSS } from '../../Theme';
4
5
 
5
6
  const Chip = ({
6
7
  icon = <></>,
@@ -8,12 +9,22 @@ const Chip = ({
8
9
  backgroundColor = '$surface_default',
9
10
  textColor = '$on_surface_high',
10
11
  hideIfNoContent = false,
12
+ onClick,
13
+ css = {},
14
+ }: {
15
+ icon?: React.JSX.Element;
16
+ content: string;
17
+ backgroundColor?: string;
18
+ textColor?: string;
19
+ hideIfNoContent?: boolean;
20
+ onClick?: () => void | Promise<void>;
21
+ css?: CSS;
11
22
  }) => {
12
23
  if (hideIfNoContent && !content) {
13
- return;
24
+ return null;
14
25
  }
15
26
  return (
16
- <Flex align="center" css={{ backgroundColor, p: '$4 $6', borderRadius: '$4' }}>
27
+ <Flex align="center" css={{ backgroundColor, p: '$4 $6', borderRadius: '$4', ...css }} onClick={() => onClick?.()}>
17
28
  {icon}
18
29
  <Text variant="sm" css={{ fontWeight: '$semiBold', color: textColor, ml: '$2' }}>
19
30
  {content}
@@ -127,13 +127,15 @@ const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter
127
127
  flex: '1 1 0',
128
128
  }}
129
129
  >
130
- <RoleAccordion
131
- peerList={handRaisedList}
132
- roleName="Hand Raised"
133
- filter={filter}
134
- isConnected={isConnected}
135
- isHandRaisedAccordion
136
- />
130
+ {handRaisedList.length > 0 ? (
131
+ <RoleAccordion
132
+ peerList={handRaisedList}
133
+ roleName="Hand Raised"
134
+ filter={filter}
135
+ isConnected={isConnected}
136
+ isHandRaisedAccordion
137
+ />
138
+ ) : null}
137
139
  {Object.keys(peersOrderedByRoles).map(role => (
138
140
  <RoleAccordion
139
141
  key={role}
@@ -179,7 +181,10 @@ export const Participant = ({ peer, isConnected }) => {
179
181
  const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
180
182
  const isHandRaised = useHMSStore(selectHasPeerHandRaised(peerId));
181
183
  const canChangeRole = useHMSStore(selectPermissions)?.changeRole;
182
- const shouldShowMoreActions = canChangeRole;
184
+ const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers;
185
+ const { elements } = useRoomLayoutConferencingScreen();
186
+ const { on_stage_exp } = elements || {};
187
+ const shouldShowMoreActions = (on_stage_exp && canChangeRole) || canRemoveOthers;
183
188
  const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId));
184
189
 
185
190
  return (
@@ -210,15 +215,21 @@ const ParticipantActions = React.memo(({ peerId, role, isLocal }) => {
210
215
  </Flex>
211
216
  ) : null}
212
217
 
213
- {shouldShowMoreActions && !isLocal ? <ParticipantMoreActions peerId={peerId} role={role} /> : null}
218
+ {shouldShowMoreActions && !isLocal ? (
219
+ <ParticipantMoreActions
220
+ peerId={peerId}
221
+ role={role}
222
+ elements={elements}
223
+ canChangeRole={canChangeRole}
224
+ canRemoveOthers={canRemoveOthers}
225
+ />
226
+ ) : null}
214
227
  </Flex>
215
228
  );
216
229
  });
217
230
 
218
- const ParticipantMoreActions = ({ peerId, role }) => {
231
+ const ParticipantMoreActions = ({ peerId, role, elements, canChangeRole, canRemoveOthers }) => {
219
232
  const hmsActions = useHMSActions();
220
- const { changeRole: canChangeRole, removeOthers: canRemoveOthers } = useHMSStore(selectPermissions);
221
- const { elements } = useRoomLayoutConferencingScreen();
222
233
  const {
223
234
  bring_to_stage_label,
224
235
  remove_from_stage_label,
@@ -327,7 +338,7 @@ export const ParticipantSearch = ({ onSearch, placeholder, inSidePane = false })
327
338
  <Input
328
339
  type="text"
329
340
  placeholder={placeholder || 'Search for participants'}
330
- css={{ w: '100%', p: '$6', pl: '$14', mr: '$4', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
341
+ css={{ w: '100%', p: '$6', pl: '$16', mr: '$4', bg: inSidePane ? '$surface_default' : '$surface_dim' }}
331
342
  value={value}
332
343
  onKeyDown={event => {
333
344
  event.stopPropagation();