Skip to content

Commit

Permalink
[Storybook v8] Add file for Participant List (#4818)
Browse files Browse the repository at this point in the history
* Add files for ParticipantList
  • Loading branch information
AmyL219 authored Jul 5, 2024
1 parent 30162a0 commit 574a59f
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 4 deletions.
46 changes: 46 additions & 0 deletions packages/storybook8/stories/Components/ParticipantList/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ParticipantList } from '@azure/communication-react';
import { Canvas, Meta } from '@storybook/blocks';
import * as ParticipantStories from './index.stories';

import DefaultCallParticipantListExampleText from '!!raw-loader!./snippets/DefaultCall.snippet.tsx';
import DefaultChatParticipantListExampleText from '!!raw-loader!./snippets/DefaultChat.snippet.tsx';
import InteractiveCallParticipantListExampleText from '!!raw-loader!./snippets/InteractiveCall.snippet.tsx';
import ParticipantListWithExcludedUserExampleText from '!!raw-loader!./snippets/WithExcludedUser.snippet.tsx';

<Meta of={ParticipantStories} component={ParticipantList} />

# ParticipantList

ParticipantList renders a list of all calling or chat participants.

## Default example for Chat

The ParticipantList for chat is by default a list of [ParticipantItem](./?path=/docs/ui-components-participantitem--participant-item) components linked with state around each chat participant.

<Canvas of={ParticipantStories.DefaultChatParticipantListDocsOnly} source={{ code: DefaultChatParticipantListExampleText }} />

## Default example for Calling

ParticipantList for calling is by default a list of [ParticipantItem](./?path=/docs/ui-components-participantitem--participant-item) components with presence linked to the participant call state, as well as icons for microphone and screen sharing states.

<Canvas of={ParticipantStories.DefaultCallParticipantListDocsOnly} source={{ code: DefaultCallParticipantListExampleText }} />

## ParticipantList with local user excluded from the list

Local user can be excluded from the participant list as shown in the example below.

<Canvas of={ParticipantStories.ParticipantListWithExcludedUserDocsOnly} source={{ code: ParticipantListWithExcludedUserExampleText }} />

## Interactive Call example

ParticipantList is designed with a rendering override, `onRenderParticipant`, which allows you to have your own design or use your own [ParticipantItem](./?path=/docs/ui-components-participantitem--participant-item) components with their context menu style enabling interaction with this participant. For example, you can add menu items and icons to the participants using `menuItems` and `onRenderIcon` properties of [ParticipantItem](./?path=/docs/ui-components-participantitem--participant-item#props) like in the code below.

For simplicity, React `useState` is used to keep the state of every participant to decide which menu items and icons to show. You can now mute and unmute by clicking a participant in the rendered example below.

Note: Each `ParticipantItem` needs a unique key to avoid warnings for children in a list.

<Canvas of={ParticipantStories.InteractiveCallParticipantListDocsOnly} source={{ code: InteractiveCallParticipantListExampleText }} />

## Props

tbd..
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { ParticipantList as ParticipantListComponent, ParticipantListParticipant } from '@azure/communication-react';
import { Stack } from '@fluentui/react';
import React from 'react';

const ParticipantListStory: (args) => JSX.Element = (args) => {
const participantsControls = [...args.remoteParticipants, ...args.localParticipant];

const mockParticipants: ParticipantListParticipant[] = participantsControls.map((p, i) => {
return {
userId: `userId ${i}`,
displayName: p.name,
state: p.status,
isMuted: p.isMuted,
isScreenSharing: p.isScreenSharing,
isRemovable: true
};
});

const myUserId = mockParticipants[mockParticipants.length - 1].userId;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onParticipantRemove = (_userId: string): void => {
// Do something when remove a participant from list
};

return (
<Stack>
<ParticipantListComponent
participants={mockParticipants}
myUserId={myUserId}
excludeMe={args.excludeMe}
onRemoveParticipant={onParticipantRemove}
/>
</Stack>
);
};

export const ParticipantList = ParticipantListStory.bind({});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { ParticipantList as ParticipantListComponent } from '@azure/communication-react';
import { Meta } from '@storybook/react';
import { controlsToAdd, defaultLocalParticipant, defaultRemoteParticipants, hiddenControl } from '../../controlsUtils';
import { DefaultCallParticipantListExample } from './snippets/DefaultCall.snippet';
import { DefaultChatParticipantListExample } from './snippets/DefaultChat.snippet';
import { InteractiveCallParticipantListExample } from './snippets/InteractiveCall.snippet';
import { ParticipantListWithExcludedUserExample } from './snippets/WithExcludedUser.snippet';
export { ParticipantList } from './ParticipantList.story';

export const DefaultCallParticipantListDocsOnly = {
render: DefaultCallParticipantListExample
};

export const DefaultChatParticipantListDocsOnly = {
render: DefaultChatParticipantListExample
};

export const InteractiveCallParticipantListDocsOnly = {
render: InteractiveCallParticipantListExample
};

export const ParticipantListWithExcludedUserDocsOnly = {
render: ParticipantListWithExcludedUserExample
};

const meta: Meta = {
title: 'Components/Participant List',
component: ParticipantListComponent,
argTypes: {
excludeMe: controlsToAdd.excludeMeFromList,
localParticipant: controlsToAdd.localParticipant,
remoteParticipants: controlsToAdd.remoteParticipants,
// Hiding auto-generated controls
participants: hiddenControl,
myUserId: hiddenControl,
onRenderParticipant: hiddenControl,
onRenderAvatar: hiddenControl,
onParticipantRemove: hiddenControl,
onRemoveParticipant: hiddenControl,
onFetchParticipantMenuItems: hiddenControl,
styles: hiddenControl
},
args: {
excludeMe: false,
localParticipant: defaultLocalParticipant,
remoteParticipants: defaultRemoteParticipants
}
} as Meta;

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { CallParticipantListParticipant, FluentThemeProvider, ParticipantList } from '@azure/communication-react';
import { Stack } from '@fluentui/react';
import React from 'react';

export const DefaultCallParticipantListExample: () => JSX.Element = () => {
const mockParticipants: CallParticipantListParticipant[] = [
{
userId: 'user1',
displayName: 'You',
state: 'Connected',
isMuted: true,
isScreenSharing: false,
isRemovable: true
},
{
userId: 'user2',
displayName: 'Hal Jordan',
state: 'Connected',
isMuted: true,
isScreenSharing: true,
isRemovable: true
},
{
userId: 'user3',
displayName: 'Barry Allen',
state: 'Idle',
isMuted: false,
isScreenSharing: false,
isRemovable: true,
raisedHand: { raisedHandOrderPosition: 1 }
},
{
userId: 'user4',
displayName: 'Bruce Wayne',
state: 'Connecting',
isMuted: false,
isScreenSharing: false,
isRemovable: false
}
];

return (
<FluentThemeProvider>
<Stack>
<div style={{ fontSize: '1.5rem', marginBottom: '1rem', fontFamily: 'Segoe UI' }}>Participants</div>
<ParticipantList participants={mockParticipants} myUserId={'user1'} />
</Stack>
</FluentThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ParticipantListParticipant, ParticipantList } from '@azure/communication-react';
import { Stack } from '@fluentui/react';
import React from 'react';

export const DefaultChatParticipantListExample: () => JSX.Element = () => {
const mockParticipants: ParticipantListParticipant[] = [
{
userId: 'user 1',
displayName: 'You',
isRemovable: true
},
{
userId: 'user 2',
displayName: 'Hal Jordan',
isRemovable: true
},
{
userId: 'user 3',
displayName: 'Barry Allen',
isRemovable: true
},
{
userId: 'user 4',
displayName: 'Bruce Wayne',
isRemovable: true
}
];

return (
<Stack>
<div style={{ fontSize: '1.5rem', marginBottom: '1rem', fontFamily: 'Segoe UI' }}>Participants</div>
<ParticipantList participants={mockParticipants} />
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {
CallParticipantListParticipant,
FluentThemeProvider,
ParticipantList,
ParticipantItem,
ParticipantListParticipant
} from '@azure/communication-react';
import { Icon, IContextualMenuItem, PersonaPresence, Stack } from '@fluentui/react';
import React, { useState } from 'react';

const mockParticipants: CallParticipantListParticipant[] = [
{
userId: 'user1',
displayName: 'You',
state: 'Connected',
isMuted: true,
isRemovable: true
},
{
userId: 'user2',
displayName: 'Peter Parker',
state: 'Connected',
isMuted: false,
isRemovable: true
},
{
userId: 'user3',
displayName: 'Matthew Murdock',
state: 'Idle',
isMuted: false,
isRemovable: true
},
{
userId: 'user4',
displayName: 'Frank Castiglione',
state: 'Connecting',
isMuted: false,
isRemovable: false
}
];

export const InteractiveCallParticipantListExample: () => JSX.Element = () => {
const [participants, setParticpants] = useState<any[]>(mockParticipants);

const mockMyUserId = 'user1';

const onRenderParticipant = (participant: ParticipantListParticipant): JSX.Element => {
const participantIndex = participants.map((p) => p.userId).indexOf(participant.userId);

const callingParticipant = participants[participantIndex] as CallParticipantListParticipant;

let presence: PersonaPresence | undefined = undefined;
if (callingParticipant) {
if (callingParticipant.state === 'Connected') {
presence = PersonaPresence.online;
} else if (callingParticipant.state === 'Idle') {
presence = PersonaPresence.away;
} else if (callingParticipant.state === 'Connecting') {
presence = PersonaPresence.offline;
}
}

const menuItems: IContextualMenuItem[] = [
{
key: 'mute',
text: callingParticipant.isMuted ? 'Unmute' : 'Mute',
onClick: () => {
const newParticipants = [...participants];
newParticipants[participantIndex].isMuted = !participants[participantIndex].isMuted;
setParticpants(newParticipants);
}
}
];

const onRenderIcon = callingParticipant?.isMuted ? () => <Icon iconName="MicOff2" /> : () => <></>;

if (participant.displayName) {
return (
<ParticipantItem
displayName={participant.displayName}
me={participant.userId === mockMyUserId}
menuItems={menuItems}
presence={presence}
onRenderIcon={onRenderIcon}
/>
);
}
return <></>;
};

return (
<FluentThemeProvider>
<Stack>
<div style={{ fontSize: '1.5rem', marginBottom: '1rem', fontFamily: 'Segoe UI' }}>Participants</div>
<ParticipantList
participants={participants}
myUserId={mockMyUserId}
onRenderParticipant={onRenderParticipant}
/>
</Stack>
</FluentThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CallParticipantListParticipant, FluentThemeProvider, ParticipantList } from '@azure/communication-react';
import { Stack } from '@fluentui/react';
import React from 'react';

const mockParticipants: CallParticipantListParticipant[] = [
{
userId: 'user1',
displayName: 'You',
state: 'Connected',
isMuted: true,
isScreenSharing: false,
isRemovable: true
},
{
userId: 'user2',
displayName: 'Hal Jordan',
state: 'Connected',
isMuted: true,
isScreenSharing: true,
isRemovable: true
},
{
userId: 'user3',
displayName: 'Barry Allen',
state: 'Idle',
isMuted: false,
isScreenSharing: false,
isRemovable: true
},
{
userId: 'user4',
displayName: 'Bruce Wayne',
state: 'Connecting',
isMuted: false,
isScreenSharing: false,
isRemovable: false
}
];

export const ParticipantListWithExcludedUserExample: () => JSX.Element = () => {
return (
<FluentThemeProvider>
<Stack>
<div style={{ fontSize: '1.5rem', marginBottom: '1rem', fontFamily: 'Segoe UI' }}>Participants</div>
<ParticipantList participants={mockParticipants} myUserId={'user1'} excludeMe={true} />
</Stack>
</FluentThemeProvider>
);
};
Loading

0 comments on commit 574a59f

Please sign in to comment.