|
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({ |
28 | 9 | chatHeader: { |
29 | | - display: 'flex', |
30 | | - alignItems: 'center', |
31 | | - columnGap: '4px', |
32 | | - lineHeight: '32px', |
33 | | - fontSize: '18px', |
34 | | - fontWeight: 700 |
35 | | - }, |
36 | | - container: { |
37 | 10 | display: 'flex', |
38 | 11 | flexDirection: 'column', |
39 | | - gridRowGap: '16px', |
40 | | - minWidth: '300px', |
41 | | - backgroundColor: tokens.colorNeutralBackground1 |
| 12 | + rowGap: 0, |
| 13 | + zIndex: 2 |
42 | 14 | }, |
43 | | - formButtons: { |
| 15 | + secondRow: { |
44 | 16 | display: 'flex', |
45 | 17 | 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 |
48 | 24 | } |
49 | 25 | }); |
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(); |
141 | 32 | return ( |
142 | 33 | <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" /> |
148 | 50 | </div> |
149 | 51 | ); |
150 | 52 | }; |
151 | 53 |
|
152 | | -export default memo(ChatHeader); |
| 54 | +export { ChatHeader }; |
0 commit comments