diff --git a/packages/storybook8/stories/Components/ParticipantList/Docs.mdx b/packages/storybook8/stories/Components/ParticipantList/Docs.mdx
new file mode 100644
index 00000000000..8be58fb5d62
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/Docs.mdx
@@ -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';
+
+
+
+# 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.
+
+
+
+## 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.
+
+
+
+## ParticipantList with local user excluded from the list
+
+Local user can be excluded from the participant list as shown in the example below.
+
+
+
+## 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.
+
+
+
+## Props
+
+tbd..
diff --git a/packages/storybook8/stories/Components/ParticipantList/ParticipantList.story.tsx b/packages/storybook8/stories/Components/ParticipantList/ParticipantList.story.tsx
new file mode 100644
index 00000000000..2c767983e6e
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/ParticipantList.story.tsx
@@ -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 (
+
+
+
+ );
+};
+
+export const ParticipantList = ParticipantListStory.bind({});
diff --git a/packages/storybook8/stories/Components/ParticipantList/index.stories.tsx b/packages/storybook8/stories/Components/ParticipantList/index.stories.tsx
new file mode 100644
index 00000000000..2ff9c41f39e
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/index.stories.tsx
@@ -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;
diff --git a/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultCall.snippet.tsx b/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultCall.snippet.tsx
new file mode 100644
index 00000000000..4c331fad224
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultCall.snippet.tsx
@@ -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 (
+
+
+ Participants
+
+
+
+ );
+};
diff --git a/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultChat.snippet.tsx b/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultChat.snippet.tsx
new file mode 100644
index 00000000000..2c8999bf4f0
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/snippets/DefaultChat.snippet.tsx
@@ -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 (
+
+ Participants
+
+
+ );
+};
diff --git a/packages/storybook8/stories/Components/ParticipantList/snippets/InteractiveCall.snippet.tsx b/packages/storybook8/stories/Components/ParticipantList/snippets/InteractiveCall.snippet.tsx
new file mode 100644
index 00000000000..cb8d3540fa4
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/snippets/InteractiveCall.snippet.tsx
@@ -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(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 ? () => : () => <>>;
+
+ if (participant.displayName) {
+ return (
+
+ );
+ }
+ return <>>;
+ };
+
+ return (
+
+
+ Participants
+
+
+
+ );
+};
diff --git a/packages/storybook8/stories/Components/ParticipantList/snippets/WithExcludedUser.snippet.tsx b/packages/storybook8/stories/Components/ParticipantList/snippets/WithExcludedUser.snippet.tsx
new file mode 100644
index 00000000000..bf328e6ed36
--- /dev/null
+++ b/packages/storybook8/stories/Components/ParticipantList/snippets/WithExcludedUser.snippet.tsx
@@ -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 (
+
+
+ Participants
+
+
+
+ );
+};
diff --git a/packages/storybook8/stories/controlsUtils.ts b/packages/storybook8/stories/controlsUtils.ts
index e4bf45aa1d6..0e3cd44ce70 100644
--- a/packages/storybook8/stories/controlsUtils.ts
+++ b/packages/storybook8/stories/controlsUtils.ts
@@ -73,8 +73,8 @@ const defaultControlsGridParticipants = [
}
];
-const defaultLocalParticipant = [{ name: 'You', status: 'Connected', isMuted: false, isScreenSharing: false }];
-const defaultRemoteParticipants = [
+export const defaultLocalParticipant = [{ name: 'You', status: 'Connected', isMuted: false, isScreenSharing: false }];
+export const defaultRemoteParticipants = [
{ name: 'Rick', status: 'InLobby', isMuted: false, isScreenSharing: false },
{ name: 'Daryl', status: 'Connecting', isMuted: false, isScreenSharing: false },
{ name: 'Michonne', status: 'Idle', isMuted: false, isScreenSharing: false }
@@ -298,7 +298,7 @@ export const controlsToAdd = {
name: 'Width (px)'
},
localParticipantDisplayName: { control: 'text', defaultValue: 'John Doe', name: 'Local Participant displayName' },
- localParticipant: { control: 'object', defaultValue: defaultLocalParticipant, name: 'Your information' },
+ localParticipant: { control: 'object', name: 'Your information' },
localVideoInverted: { control: 'boolean', defaultValue: true, name: 'Invert Local Video' },
localVideoStreamEnabled: { control: 'boolean', defaultValue: true, name: 'Turn Local Video On' },
messageDeliveredTooltipText: { control: 'text', defaultValue: 'Sent', name: 'Delivered icon tooltip text' },
@@ -335,7 +335,7 @@ export const controlsToAdd = {
'Rick, Daryl, Michonne, Dwight, Pam, Michael, Jim, Kevin, Creed, Angela, Andy, Stanley, Meredith, Phyllis, Oscar, Ryan, Kelly, Andy, Toby, Darryl, Gabe, Erin',
name: 'Remote participants (comma separated)'
},
- remoteParticipants: { control: 'object', defaultValue: defaultRemoteParticipants, name: 'Remote participants' },
+ remoteParticipants: { control: 'object', name: 'Remote participants' },
requiredDisplayName: {
control: 'text',
defaultValue: 'John Smith',