diff --git a/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx b/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx
new file mode 100644
index 00000000..febf6489
--- /dev/null
+++ b/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx
@@ -0,0 +1,86 @@
+import { useSnapshot } from 'valtio'
+import { layoutStore } from '@/stores/layout'
+import { css } from '@/styled-system/css'
+import { Heading } from 'react-aria-components'
+import { text } from '@/primitives/Text'
+import { Box, Button, Div } from '@/primitives'
+import { RiCloseLine } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+import { ParticipantsList } from './controls/Participants/ParticipantsList'
+import { useWidgetInteraction } from '../hooks/useWidgetInteraction'
+import { ReactNode } from 'react'
+
+type StyledSidePanelProps = {
+ title: string
+ children: ReactNode
+ onClose: () => void
+ closeButtonTooltip: string
+}
+
+const StyledSidePanel = ({
+ title,
+ children,
+ onClose,
+ closeButtonTooltip,
+}: StyledSidePanelProps) => (
+
+
+ {title}
+
+
+
+
+ {children}
+
+)
+
+export const SidePanel = () => {
+ const layoutSnap = useSnapshot(layoutStore)
+ const sidePanel = layoutSnap.sidePanel
+
+ const { isParticipantsOpen } = useWidgetInteraction()
+ const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
+
+ if (!sidePanel) {
+ return
+ }
+
+ return (
+ (layoutStore.sidePanel = null)}
+ closeButtonTooltip={t('closeButton', {
+ content: t(`content.${sidePanel}`),
+ })}
+ >
+ {isParticipantsOpen && }
+
+ )
+}
diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Participants/ParticipantsList.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Participants/ParticipantsList.tsx
index 1144db1b..6c79ee9c 100644
--- a/src/frontend/src/features/rooms/livekit/components/controls/Participants/ParticipantsList.tsx
+++ b/src/frontend/src/features/rooms/livekit/components/controls/Participants/ParticipantsList.tsx
@@ -1,17 +1,13 @@
import { css } from '@/styled-system/css'
import { useParticipants } from '@livekit/components-react'
-import { Heading } from 'react-aria-components'
-import { Box, Button, Div, H } from '@/primitives'
-import { text } from '@/primitives/Text'
-import { RiCloseLine } from '@remixicon/react'
-import { participantsStore } from '@/stores/participants'
+import { Div, H } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { allParticipantRoomEvents } from '@/features/rooms/livekit/constants/events'
-import { ParticipantListItem } from '@/features/rooms/livekit/components/controls/Participants/ParticipantListItem'
-import { ParticipantsCollapsableList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsCollapsableList'
-import { HandRaisedListItem } from '@/features/rooms/livekit/components/controls/Participants/HandRaisedListItem'
-import { LowerAllHandsButton } from '@/features/rooms/livekit/components/controls/Participants/LowerAllHandsButton'
+import { ParticipantListItem } from '../../controls/Participants/ParticipantListItem'
+import { ParticipantsCollapsableList } from '../../controls/Participants/ParticipantsCollapsableList'
+import { HandRaisedListItem } from '../../controls/Participants/HandRaisedListItem'
+import { LowerAllHandsButton } from '../../controls/Participants/LowerAllHandsButton'
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
export const ParticipantsList = () => {
@@ -44,76 +40,40 @@ export const ParticipantsList = () => {
// TODO - extract inline styling in a centralized styling file, and avoid magic numbers
return (
-
-
+
- {t('heading')}
-
-
-
-
-
-
- {t('subheading').toUpperCase()}
-
- {raisedHandParticipants.length > 0 && (
-
-
(
-
- )}
- action={() => (
-
- )}
- />
-
+ {t('subheading').toUpperCase()}
+
+ {raisedHandParticipants.length > 0 && (
+
+
(
+
+ )}
+ action={() => (
+
+ )}
+ />
+
+ )}
+
(
+
)}
- (
-
- )}
- />
-
-
+ />
+ >
)
}
diff --git a/src/frontend/src/features/rooms/livekit/hooks/useWidgetInteraction.ts b/src/frontend/src/features/rooms/livekit/hooks/useWidgetInteraction.ts
index 09f92cab..1c158709 100644
--- a/src/frontend/src/features/rooms/livekit/hooks/useWidgetInteraction.ts
+++ b/src/frontend/src/features/rooms/livekit/hooks/useWidgetInteraction.ts
@@ -1,23 +1,25 @@
import { useLayoutContext } from '@livekit/components-react'
import { useSnapshot } from 'valtio'
-import { participantsStore } from '@/stores/participants.ts'
+import { layoutStore } from '@/stores/layout'
export const useWidgetInteraction = () => {
const { dispatch, state } = useLayoutContext().widget
- const participantsSnap = useSnapshot(participantsStore)
- const isParticipantsOpen = participantsSnap.showParticipants
+ const layoutSnap = useSnapshot(layoutStore)
+ const sidePanel = layoutSnap.sidePanel
+
+ const isParticipantsOpen = sidePanel == 'participants'
const toggleParticipants = () => {
if (dispatch && state?.showChat) {
dispatch({ msg: 'toggle_chat' })
}
- participantsStore.showParticipants = !isParticipantsOpen
+ layoutStore.sidePanel = isParticipantsOpen ? null : 'participants'
}
const toggleChat = () => {
if (isParticipantsOpen) {
- participantsStore.showParticipants = false
+ layoutStore.sidePanel = null
}
if (dispatch) {
dispatch({ msg: 'toggle_chat' })
diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
index 5504a370..c78a5632 100644
--- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
+++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
@@ -30,11 +30,11 @@ import {
import { ControlBar } from './ControlBar'
import { styled } from '@/styled-system/jsx'
import { cva } from '@/styled-system/css'
-import { ParticipantsList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsList'
import { useSnapshot } from 'valtio'
-import { participantsStore } from '@/stores/participants'
+import { layoutStore } from '@/stores/layout'
import { FocusLayout } from '../components/FocusLayout'
import { ParticipantTile } from '../components/ParticipantTile'
+import { SidePanel } from '../components/SidePanel'
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
const LayoutWrapper = styled(
@@ -172,8 +172,8 @@ export function VideoConference({
])
/* eslint-enable react-hooks/exhaustive-deps */
- const participantsSnap = useSnapshot(participantsStore)
- const showParticipants = participantsSnap.showParticipants
+ const layoutSnap = useSnapshot(layoutStore)
+ const sidePanel = layoutSnap.sidePanel
return (
@@ -223,7 +223,7 @@ export function VideoConference({
messageEncoder={chatMessageEncoder}
messageDecoder={chatMessageDecoder}
/>
- {showParticipants &&
}
+ {sidePanel &&
}
diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json
index ab9779e0..c06c160f 100644
--- a/src/frontend/src/locales/de/rooms.json
+++ b/src/frontend/src/locales/de/rooms.json
@@ -73,10 +73,17 @@
"effects": ""
}
},
+ "sidePanel": {
+ "heading": {
+ "participants": ""
+ },
+ "content": {
+ "participants": ""
+ },
+ "closeButton": ""
+ },
"participants": {
- "heading": "",
"subheading": "",
- "closeButton": "",
"contributors": "",
"collapsable": {
"open": "",
diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json
index 1a4baa71..294d2806 100644
--- a/src/frontend/src/locales/en/rooms.json
+++ b/src/frontend/src/locales/en/rooms.json
@@ -71,10 +71,17 @@
"effects": "Apply effects"
}
},
+ "sidePanel": {
+ "heading": {
+ "participants": "Participants"
+ },
+ "content": {
+ "participants": "participants"
+ },
+ "closeButton": "Hide {{content}}"
+ },
"participants": {
- "heading": "Participants",
"subheading": "In room",
- "closeButton": "Hide participants",
"you": "You",
"contributors": "Contributors",
"collapsable": {
diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json
index 587164b0..6f2379f3 100644
--- a/src/frontend/src/locales/fr/rooms.json
+++ b/src/frontend/src/locales/fr/rooms.json
@@ -71,10 +71,17 @@
"effects": "Appliquer des effets"
}
},
+ "sidePanel": {
+ "heading": {
+ "participants": "Participants"
+ },
+ "content": {
+ "participants": "les participants"
+ },
+ "closeButton": "Masquer {{content}}"
+ },
"participants": {
- "heading": "Participants",
"subheading": "Dans la réunion",
- "closeButton": "Masquer les participants",
"you": "Vous",
"contributors": "Contributeurs",
"collapsable": {
diff --git a/src/frontend/src/stores/layout.ts b/src/frontend/src/stores/layout.ts
index bee6baed..70cb4e8d 100644
--- a/src/frontend/src/stores/layout.ts
+++ b/src/frontend/src/stores/layout.ts
@@ -2,8 +2,10 @@ import { proxy } from 'valtio'
type State = {
showHeader: boolean
+ sidePanel: 'participants' | null
}
export const layoutStore = proxy({
showHeader: false,
+ sidePanel: null,
})
diff --git a/src/frontend/src/stores/participants.ts b/src/frontend/src/stores/participants.ts
deleted file mode 100644
index 0fdc7ed9..00000000
--- a/src/frontend/src/stores/participants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { proxy } from 'valtio'
-
-type State = {
- showParticipants: boolean
-}
-
-export const participantsStore = proxy({
- showParticipants: false,
-})