Add custom sidebar
Source Code: https://github.com/dyte-io/react-samples/tree/main/samples/create-your-own-ui
To create a sidebar of your own, you need 2 things.
- A custom sidebar UI
- An action button to trigger the UI
LIVE EDITOR
import { DyteStage, DyteGrid, DyteNotifications, DyteSidebar, DyteControlbar, DyteParticipantsAudio, DyteDialogManager, DyteControlbarButton, DyteSidebarUi, defaultIconPack, defaultConfig, generateConfig } from '@dytesdk/react-ui-kit';
import { useDyteMeeting, useDyteSelector } from '@dytesdk/react-web-core';
import { useEffect, useState } from 'react';
function SidebarWithCustomUI({
meeting, states, config, setStates,
}: { meeting: DyteClient, config: UIConfig, states: CustomStates, setStates: SetStates }
){
const [tabs, setTabs] = useState([
{ id: 'chat', name: 'chat' },
{ id: 'polls', name: 'polls' },
{ id: 'participants', name: 'participants' },
{ id: 'plugins', name: 'plugins' },
{ id: 'guidelines', name: 'Guidelines' }
]);
const [view, setView] = useState<DyteSidebarView>('sidebar');
if(!states.activeSidebar || (!states.sidebar && !states.customSidebar)){
return null;
}
const currentTab = states.sidebar || states.customSidebar;
return (
<DyteSidebarUi
tabs={tabs}
currentTab={currentTab}
view={view}
onTabChange={(e) => {
setStates((oldState) => {
return {
...oldState,
activeSidebar: true,
customSidebar: e.detail,
sidebar: e.detail,
}
});
}}
className="w-80 "
onSidebarClose={() => {
setStates((oldState) => {
return {
...oldState,
activeSidebar: false,
sidebar: null,
customSidebar: null,
}
});
}}>
{currentTab === 'chat' && <DyteChat meeting={meeting} config={config} slot="chat" /> }
{currentTab === 'polls' && <DytePolls meeting={meeting} config={config} slot="polls" /> }
{currentTab === 'participants' && <DyteParticipants meeting={meeting} config={config} states={states} slot="participants" /> }
{currentTab === 'plugins' && <DytePlugins meeting={meeting} config={config} slot="plugins" /> }
{currentTab === 'guidelines' && <div slot="guidelines" className="flex justify-center items-center p-2">
<div>
<p>1. Ensure active participation and professionalism by muting your microphone when not speaking.</p>
<br></br>
<p>2. Utilize the chat feature for questions or comments during the meeting.</p>
</div>
</div> }
</DyteSidebarUi>);
}
function MeetingStage({ meeting, config, states, setStates }: { meeting: DyteClient, config: UIConfig, states: CustomStates, setStates: SetStates}) {
return (
<div className="flex h-full w-full flex-col">
<DyteStage className="flex h-full w-full flex-1 p-2">
<DyteGrid meeting={meeting} config={config} states={states} />
<DyteNotifications meeting={meeting} config={config} states={states} />
{states.activeSidebar && (
<SidebarWithCustomUI
meeting={meeting}
config={config}
states={states}
setStates={setStates}
/>
)}
</DyteStage>
<DyteParticipantsAudio meeting={meeting} />
<DyteDialogManager meeting={meeting} config={config} states={states} />
<div>
<DyteControlbarButton
onClick={() => {
if(states.activeSidebar && !states.sidebar && states.customSidebar === 'guidelines'){
setStates( (oldState) => { return { ...oldState, activeSidebar: false, sidebar: null, customSidebar: null }});
} else {
setStates( (oldState) => { return { ...oldState, activeSidebar: true, sidebar: null, customSidebar: 'guidelines' }});
}
}}
icon={defaultIconPack.add}
label={'Open Custom SideBar'}
/>
</div>
</div>
); }
export default function Meeting() {
const { meeting } = useDyteMeeting();
const [config, setConfig] = useState(defaultConfig);
const [states, setStates] = useState<CustomStates>({
meeting: 'setup',
sidebar: 'chat'
});
useEffect(() => {
async function setupMeetingConfigs(){
const theme = meeting!.self.config;
const { config } = generateConfig(theme, meeting!);
/**
* Full screen by default requests dyte-meeting/DyteMeeting element to be in full screen.
* Since DyteMeeting element is not here,
* we need to pass dyte-fullscreen-toggle, an targetElementId through config.
*/
// setFullScreenToggleTargetElement({config, targetElementId: 'root'});
setConfig({...config});
/**
* Add listeners on meeting & self to monitor leave meeting, join meeting and so on.
* This work was earlier done by DyteMeeting component internally.
*/
const stateListenersUtils = new DyteStateListenersUtils(() => meeting, () => states, () => setStates);
stateListenersUtils.addDyteEventListeners();
try{
await meeting.join();
} catch(e){
// do nothing
}
}
if(meeting){
/**
* During development phase, make sure to expose meeting object to window,
* for debugging purposes.
*/
Object.assign(window, {
meeting,
})
setupMeetingConfigs();
}
}, [meeting]);
return (
<div className="flex w-full h-full" ref={(el) => {
el?.addEventListener('dyteStateUpdate', (e) => {
const { detail: newStateUpdate } = e as unknown as { detail: CustomStates };
console.log('dyteStateUpdateSetup:: ', newStateUpdate);
setStates((oldState: CustomStates) => { return {
...oldState,
...newStateUpdate,
}});
});
}}>
<MeetingStage meeting={meeting} config={config} states={states} setStates={setStates} />
</div>
)
}
export class DyteStateListenersUtils{
getStates: () => CustomStates;
getStateSetter: () => (newState: CustomStates) => void;
getMeeting: () => DyteClient;
get states(){
return this.getStates();
}
get setGlobalStates(){
return this.getStateSetter();
};
get meeting(){
return this.getMeeting();
}
constructor(getMeeting: () => DyteClient, getGlobalStates: () => CustomStates, getGlobalStateSetter: () => (newState: CustomStates) => void){
this.getMeeting = getMeeting;
this.getStates = getGlobalStates;
this.getStateSetter = getGlobalStateSetter;
}
private updateStates(newState: CustomStates){
this.setGlobalStates((oldState: CustomStates) => { return {
...oldState,
...newState,
}});
console.log(newState);
}
private roomJoinedListener = () => {
this.updateStates({ meeting: 'joined' });
};
private socketServiceRoomJoinedListener = () => {
if (this.meeting.stage.status === 'ON_STAGE' || this.meeting.stage.status === undefined) return;
this.updateStates({ meeting: 'joined' });
};
private waitlistedListener = () => {
this.updateStates({ meeting: 'waiting' });
};
private roomLeftListener = ({ state }: { state: RoomLeftState }) => {
const states = this.states;
if (states?.roomLeftState === 'disconnected') {
this.updateStates({ meeting: 'ended', roomLeftState: state });
return;
}
this.updateStates({ meeting: 'ended', roomLeftState: state });
};
private mediaPermissionUpdateListener = ({ kind, message }: {
kind: PermissionSettings['kind'],
message: string,
}) => {
if (['audio', 'video'].includes(kind!)) {
if (message === 'ACCEPTED' || message === 'NOT_REQUESTED' || this.states.activeDebugger)
return;
const permissionModalSettings: PermissionSettings = {
enabled: true,
kind,
};
this.updateStates({ activePermissionsMessage: permissionModalSettings });
}
};
private joinStateAcceptedListener = () => {
this.updateStates({ activeJoinStage: true });
};
private handleChangingMeeting(destinationMeetingId: string) {
this.updateStates({
activeBreakoutRoomsManager: {
...this.states.activeBreakoutRoomsManager,
active: this.states.activeBreakoutRoomsManager!.active,
destinationMeetingId,
}
});
}
addDyteEventListeners(){
if (this.meeting.meta.viewType === 'LIVESTREAM') {
this.meeting.self.addListener('socketServiceRoomJoined', this.socketServiceRoomJoinedListener);
}
this.meeting.self.addListener('roomJoined', this.roomJoinedListener);
this.meeting.self.addListener('waitlisted', this.waitlistedListener);
this.meeting.self.addListener('roomLeft', this.roomLeftListener);
this.meeting.self.addListener('mediaPermissionUpdate', this.mediaPermissionUpdateListener);
this.meeting.self.addListener('joinStageRequestAccepted', this.joinStateAcceptedListener);
if (this.meeting.connectedMeetings.supportsConnectedMeetings) {
this.meeting.connectedMeetings.once('changingMeeting', this.handleChangingMeeting);
}
}
cleanupDyteEventListeners(){
}
}
Let's say, we want to show some meeting guidelines to all the participants in a side bar. To do so, in the below code snippet, we have added guidelines
sidebar section.
We have added a custom button to trigger the UI as well using DyteControlbarButton
component.
<DyteControlbarButton
onClick={() => {
if(states.activeSidebar && !states.sidebar && states.customSidebar === 'guidelines'){
setStates( (oldState) => { return { ...oldState, activeSidebar: false, sidebar: null, customSidebar: null }});
} else {
setStates( (oldState) => { return { ...oldState, activeSidebar: true, sidebar: null, customSidebar: 'guidelines' }});
}
}}
icon={defaultIconPack.add}
label={'Open Custom SideBar'}
/>
For such a sidebar extension, we will have to update the types as well if in case you are using react with Typescript.
import type { States } from "@dytesdk/ui-kit";
import { DyteSidebarSection } from "@dytesdk/ui-kit/dist/types/components/dyte-sidebar/dyte-sidebar";
export type CustomSideBarTabs = DyteSidebarSection | 'guidelines';
export type CustomStates = States & { activeMediaPreviewModal?: boolean, customSidebar?: CustomSideBarTabs }
export type SetStates = React.Dispatch<React.SetStateAction<CustomStates>>;
Now that we know how we can add a custom sidebar, we can move on to customise the DyteStage
component further.