Skip to content

Commit 112aaba

Browse files
gavinbarronmusale
andauthored
feat: add chat icons (#2789)
adds icons for group chat and meeting chats fixes overall layout of chat header refactors ChatHeader in to smaller components adds a css property for aligning items in the mgt-person fixes padding around chat messages and sendbox update person style story with new css property set cursor to pointer for chat members list items fix z-index for chat name editing popover to ensure that dividers do not render above it change hard coded theme to web light fix missing edit icon for chat renames --------- Co-authored-by: Musale Martin <[email protected]>
1 parent 80a6d07 commit 112aaba

File tree

16 files changed

+376
-228
lines changed

16 files changed

+376
-228
lines changed

.yarn/install-state.gz

-297 KB
Binary file not shown.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@
4040
"watch": "lerna run --parallel --stream --scope '@microsoft/*' --ignore '@microsoft/mgt' --ignore '@microsoft/mgt-spf*' --ignore '@microsoft/mgt-sharepoint-provider' --ignore '@microsoft/mgt-electron-provider' --ignore '@microsoft/mgt-teamsfx-provider' --ignore '@microsoft/mgt-proxy-provider' build:watch ",
4141
"watch:serve": "npm-run-all --parallel watch serve",
4242
"watch:serve:https": "npm-run-all -parallel watch serve:https",
43-
"watch:chat-library": "lerna run build:watch --scope @microsoft/mgt-chat --include-dependencies",
43+
"watch:chat-library-deps": "lerna run build:watch --parallel --stream --scope @microsoft/mgt-chat --include-dependencies",
44+
"watch:chat-library": "lerna run build:watch --scope @microsoft/mgt-chat",
4445
"watch:chat-test-app": "lerna run start --scope react-chat --include-dependencies",
45-
"watch:chat": "npm-run-all --parallel watch:chat-library watch:chat-test-app",
46+
"watch:chat": "npm-run-all --parallel watch:chat-library watch:chat-library-deps watch:chat-test-app",
4647
"watch:components": "lerna run --parallel build:watch --scope @microsoft/mgt-components --include-dependents --include-dependencies",
4748
"watch:react-contoso": "lerna run start --scope react-contoso",
4849
"watch:react": "npm-run-all --parallel watch:components watch:react-contoso",

packages/mgt-chat/src/components/Chat/Chat.tsx

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { ErrorBar, FluentThemeProvider, MessageThread, SendBox, MessageThreadStyles } from '@azure/communication-react';
22
import { FluentTheme, MessageBarType } from '@fluentui/react';
3-
import { FluentProvider, makeStyles, shorthands, teamsLightTheme } from '@fluentui/react-components';
3+
import { FluentProvider, makeStyles, shorthands, webLightTheme } from '@fluentui/react-components';
44
import { Person, PersonCardInteraction, Spinner } from '@microsoft/mgt-react';
55
import React, { useEffect, useState } from 'react';
66
import { StatefulGraphChatClient } from '../../statefulClient/StatefulGraphChatClient';
77
import { useGraphChatClient } from '../../statefulClient/useGraphChatClient';
88
import { onRenderMessage } from '../../utils/chat';
9-
import ChatHeader from '../ChatHeader/ChatHeader';
109
import ChatMessageBar from '../ChatMessageBar/ChatMessageBar';
11-
import { ManageChatMembers } from '../ManageChatMembers/ManageChatMembers';
1210
import { renderMGTMention } from '../../utils/mentions';
1311
import { registerAppIcons } from '../styles/registerIcons';
12+
import { ChatHeader } from '../ChatHeader/ChatHeader';
1413

1514
registerAppIcons();
1615

@@ -23,9 +22,11 @@ const useStyles = makeStyles({
2322
display: 'flex',
2423
flexDirection: 'column',
2524
height: '100%',
26-
...shorthands.overflow('auto')
25+
...shorthands.overflow('auto'),
26+
paddingBlockEnd: '12px'
2727
},
2828
chatMessages: {
29+
paddingInlineStart: '16px',
2930
height: 'auto',
3031
...shorthands.overflow('auto'),
3132
'& img': {
@@ -34,11 +35,9 @@ const useStyles = makeStyles({
3435
}
3536
},
3637
chatInput: {
38+
...shorthands.paddingInline('24px'),
3739
...shorthands.overflow('unset')
3840
},
39-
chatHeader: {
40-
zIndex: 2
41-
},
4241
fullHeight: {
4342
height: '100%'
4443
},
@@ -93,25 +92,11 @@ export const Chat = ({ chatId }: IMgtChatProps) => {
9392

9493
return (
9594
<FluentThemeProvider fluentTheme={FluentTheme}>
96-
<FluentProvider theme={teamsLightTheme} className={styles.fullHeight}>
95+
<FluentProvider theme={webLightTheme} className={styles.fullHeight}>
9796
<div className={styles.chat}>
9897
{chatState.userId && chatId && chatState.messages.length > 0 ? (
9998
<>
100-
<div className={styles.chatHeader}>
101-
<ChatHeader
102-
chat={chatState.chat}
103-
currentUserId={chatState.userId}
104-
onRenameChat={chatState.onRenameChat}
105-
/>
106-
{chatState.participants?.length > 0 && chatState.chat?.chatType === 'group' && (
107-
<ManageChatMembers
108-
members={chatState.participants}
109-
removeChatMember={chatState.onRemoveChatMember}
110-
currentUserId={chatState.userId}
111-
addChatMembers={chatState.onAddChatMembers}
112-
/>
113-
)}
114-
</div>
99+
<ChatHeader chatState={chatState} />
115100
<div className={styles.chatMessages}>
116101
<MessageThread
117102
userId={chatState.userId}
Lines changed: 40 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,54 @@
1-
import React, { memo, useCallback, useState } from 'react';
2-
import { AadUserConversationMember, Chat } from '@microsoft/microsoft-graph-types';
3-
import { Edit20Filled, Edit20Regular, bundleIcon } from '@fluentui/react-icons';
4-
import { Person, PersonCardInteraction, ViewType } from '@microsoft/mgt-react';
5-
import {
6-
Button,
7-
Field,
8-
Input,
9-
InputOnChangeData,
10-
OnOpenChangeData,
11-
Popover,
12-
PopoverSurface,
13-
PopoverTrigger,
14-
makeStyles,
15-
tokens
16-
} from '@fluentui/react-components';
17-
import type { PopoverProps } from '@fluentui/react-components';
18-
19-
interface ChatHeaderProps {
20-
chat?: Chat;
21-
currentUserId?: string;
22-
onRenameChat: (newName: string | null) => Promise<void>;
23-
}
24-
25-
const EditIcon = bundleIcon(Edit20Filled, Edit20Regular);
26-
27-
const useStyles = makeStyles({
1+
import { Divider, makeStyles, mergeClasses } from '@fluentui/react-components';
2+
import React, { memo } from 'react';
3+
import { GraphChatClient } from '../../statefulClient/StatefulGraphChatClient';
4+
import ChatTitle from './ChatTitle';
5+
import { ManageChatMembers } from '../ManageChatMembers/ManageChatMembers';
6+
import { useCommonHeaderStyles } from './useCommonHeaderStyles';
7+
8+
const useHeaderStyles = makeStyles({
289
chatHeader: {
29-
display: 'flex',
30-
alignItems: 'center',
31-
columnGap: '4px',
32-
lineHeight: '32px',
33-
fontSize: '18px',
34-
fontWeight: 700
35-
},
36-
container: {
3710
display: 'flex',
3811
flexDirection: 'column',
39-
gridRowGap: '16px',
40-
minWidth: '300px',
41-
backgroundColor: tokens.colorNeutralBackground1
12+
rowGap: 0,
13+
zIndex: 2
4214
},
43-
formButtons: {
15+
secondRow: {
4416
display: 'flex',
4517
flexDirection: 'row',
46-
justifyContent: 'flex-end',
47-
gridColumnGap: '8px'
18+
justifyContent: 'flex-start',
19+
alignItems: 'center',
20+
height: '30px'
21+
},
22+
noGrow: {
23+
flexGrow: 0
4824
}
4925
});
50-
51-
const reduceToFirstNamesList = (participants: AadUserConversationMember[] = [], userId = '') => {
52-
return participants
53-
.filter(p => p.userId !== userId)
54-
.map(p => {
55-
if (p.displayName?.includes(' ')) {
56-
return p.displayName.split(' ')[0];
57-
}
58-
return p.displayName || p.email || p.id;
59-
})
60-
.join(', ');
61-
};
62-
63-
const GroupChatHeader = ({ chat, currentUserId, onRenameChat }: ChatHeaderProps) => {
64-
const styles = useStyles();
65-
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
66-
const handleOpenChange = useCallback((_, data: OnOpenChangeData) => setIsPopoverOpen(data.open || false), []);
67-
const onCancelClicked = useCallback(() => setIsPopoverOpen(false), []);
68-
const [chatName, setChatName] = useState(chat?.topic);
69-
const onChatNameChanged = useCallback(
70-
(_: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, data: InputOnChangeData) => {
71-
setChatName(data.value);
72-
},
73-
[]
74-
);
75-
const renameChat = useCallback(() => {
76-
void onRenameChat(chatName || null);
77-
}, [chatName, onRenameChat]);
78-
const chatTitle = chat?.topic
79-
? chat.topic
80-
: reduceToFirstNamesList(chat?.members as AadUserConversationMember[], currentUserId);
81-
const trapFocus = true;
82-
const popoverProps: Partial<PopoverProps> = {
83-
trapFocus,
84-
inertTrapFocus: trapFocus,
85-
inline: true,
86-
positioning: 'below-start',
87-
open: isPopoverOpen,
88-
onOpenChange: handleOpenChange
89-
};
90-
return (
91-
<>
92-
{chatTitle}
93-
<Popover {...popoverProps}>
94-
<PopoverTrigger>
95-
<Button appearance="transparent" icon={<EditIcon />} aria-label="Name group chat"></Button>
96-
</PopoverTrigger>
97-
<PopoverSurface>
98-
<div className={styles.container}>
99-
<Field label="Group name">
100-
<Input placeholder="Enter group name" onChange={onChatNameChanged} value={chatName || ''} />
101-
</Field>
102-
<div className={styles.formButtons}>
103-
<Button appearance="secondary" onClick={onCancelClicked}>
104-
Cancel
105-
</Button>
106-
<Button appearance="primary" disabled={chatName === chat?.topic} onClick={renameChat}>
107-
Send
108-
</Button>
109-
</div>
110-
</div>
111-
</PopoverSurface>
112-
</Popover>
113-
</>
114-
);
115-
};
116-
117-
const OneToOneChatHeader = ({ chat, currentUserId }: ChatHeaderProps) => {
118-
const id = getOtherParticipantUserId(chat, currentUserId);
119-
return id ? (
120-
<Person
121-
userId={id}
122-
view={ViewType.oneline}
123-
avatarSize="small"
124-
personCardInteraction={PersonCardInteraction.hover}
125-
showPresence={true}
126-
/>
127-
) : null;
128-
};
129-
130-
const getOtherParticipantUserId = (chat?: Chat, currentUserId = '') =>
131-
(chat?.members as AadUserConversationMember[])?.find(m => m.userId !== currentUserId)?.userId;
132-
133-
/**
134-
* Shows the "name" of the chat.
135-
* For group chats where the topic is set it will show the topic.
136-
* For group chats with no topic it will show the first names of other participants comma separated.
137-
* For 1:1 chats it will show the first name of the other participant.
138-
*/
139-
const ChatHeader = ({ chat, currentUserId, onRenameChat }: ChatHeaderProps) => {
140-
const styles = useStyles();
26+
interface ChatHeaderProps {
27+
chatState: GraphChatClient;
28+
}
29+
const ChatHeader = ({ chatState }: ChatHeaderProps) => {
30+
const styles = useHeaderStyles();
31+
const commonStyles = useCommonHeaderStyles();
14132
return (
14233
<div className={styles.chatHeader}>
143-
{chat?.chatType === 'group' ? (
144-
<GroupChatHeader chat={chat} currentUserId={currentUserId} onRenameChat={onRenameChat} />
145-
) : (
146-
<OneToOneChatHeader chat={chat} currentUserId={currentUserId} onRenameChat={onRenameChat} />
147-
)}
34+
<ChatTitle chat={chatState.chat} currentUserId={chatState.userId} onRenameChat={chatState.onRenameChat} />
35+
<Divider appearance="subtle" />
36+
<div className={mergeClasses(styles.secondRow, commonStyles.row)}>
37+
{chatState.participants?.length > 0 && chatState.chat?.chatType === 'group' && (
38+
<>
39+
<ManageChatMembers
40+
members={chatState.participants}
41+
removeChatMember={chatState.onRemoveChatMember}
42+
currentUserId={chatState.userId}
43+
addChatMembers={chatState.onAddChatMembers}
44+
/>
45+
<Divider vertical appearance="subtle" inset className={styles.noGrow} />
46+
</>
47+
)}
48+
</div>
49+
<Divider appearance="subtle" />
14850
</div>
14951
);
15052
};
15153

152-
export default memo(ChatHeader);
54+
export { ChatHeader };
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { memo } from 'react';
2+
import { Chat } from '@microsoft/microsoft-graph-types';
3+
import { makeStyles, mergeClasses } from '@fluentui/react-components';
4+
import { useCommonHeaderStyles } from './useCommonHeaderStyles';
5+
import { OneToOneChatHeader } from './OneToOneChatHeader';
6+
import { GroupChatHeader } from './GroupChatHeader';
7+
8+
export interface ChatHeaderProps {
9+
chat?: Chat;
10+
currentUserId?: string;
11+
onRenameChat: (newName: string | null) => Promise<void>;
12+
}
13+
14+
const useTitleStyles = makeStyles({
15+
chatHeader: {
16+
display: 'flex',
17+
alignItems: 'center',
18+
columnGap: '0',
19+
lineHeight: '32px',
20+
fontSize: '18px',
21+
fontWeight: 700,
22+
marginBlockStart: '10px',
23+
marginBlockEnd: '8px',
24+
zIndex: 3
25+
}
26+
});
27+
28+
/**
29+
* Shows the "name" of the chat.
30+
* For group chats where the topic is set it will show the topic.
31+
* For group chats with no topic it will show the first names of other participants comma separated.
32+
* For 1:1 chats it will show the first name of the other participant.
33+
*/
34+
const ChatTitle = ({ chat, currentUserId, onRenameChat }: ChatHeaderProps) => {
35+
const styles = useTitleStyles();
36+
const commonStyles = useCommonHeaderStyles();
37+
return (
38+
<div className={mergeClasses(styles.chatHeader, commonStyles.row)}>
39+
{chat?.chatType !== 'oneOnOne' ? (
40+
<GroupChatHeader chat={chat} currentUserId={currentUserId} onRenameChat={onRenameChat} />
41+
) : (
42+
<OneToOneChatHeader chat={chat} currentUserId={currentUserId} onRenameChat={onRenameChat} />
43+
)}
44+
</div>
45+
);
46+
};
47+
48+
export default memo(ChatTitle);

0 commit comments

Comments
 (0)