Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Changing attach file icon position basis on form factor",
"packageName": "@internal/react-composites",
"email": "anjulgarg@live.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ export type ChatCompositeOptions = {
// @public
export interface ChatCompositeProps extends BaseCompositeProps<ChatCompositeIcons> {
adapter: ChatAdapter;
formFactor?: 'desktop' | 'mobile';
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => JSX.Element;
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;
options?: ChatCompositeOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/react-composites/review/react-composites.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ export type ChatCompositeOptions = {
// @public
export interface ChatCompositeProps extends BaseCompositeProps<ChatCompositeIcons> {
adapter: ChatAdapter;
formFactor?: 'desktop' | 'mobile';
onRenderMessage?: (messageProps: MessageProps, defaultOnRender?: MessageRenderer) => JSX.Element;
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;
options?: ChatCompositeOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ export interface ChatCompositeProps extends BaseCompositeProps<ChatCompositeIcon
* A callback for customizing the typing indicator renderer.
*/
onRenderTypingIndicator?: (typingUsers: CommunicationParticipant[]) => JSX.Element;

/**
* Flags to enable/disable visual elements of the {@link ChatComposite}.
*/
options?: ChatCompositeOptions;
/* @conditional-compile-remove(file-sharing) */
/**
* Optimizes the composite form factor for either desktop or mobile.
* @remarks `mobile` is currently only optimized for Portrait mode on mobile devices and does not support landscape.
* @defaultValue 'desktop'
*/
formFactor?: 'desktop' | 'mobile';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when this goes stable we'll have to somehow remember to update storybook docs...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made a note.

}

/**
Expand Down Expand Up @@ -97,6 +103,8 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
onFetchParticipantMenuItems
} = props;

const formFactor = props['formFactor'] || 'desktop';

/**
* @TODO Remove this function and pass the props directly when file-sharing is promoted to stable.
* @private
Expand All @@ -115,6 +123,7 @@ export const ChatComposite = (props: ChatCompositeProps): JSX.Element => {
<BaseProvider {...props}>
<ChatAdapterProvider adapter={adapter}>
<ChatScreen
formFactor={formFactor}
options={options}
onFetchAvatarPersonaData={onFetchAvatarPersonaData}
onRenderTypingIndicator={onRenderTypingIndicator}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type ChatScreenProps = {
styles?: ChatScreenStyles;
hasFocusOnMount?: 'sendBoxTextField';
fileSharing?: FileSharingOptions;
formFactor?: 'desktop' | 'mobile';
};

/**
Expand Down Expand Up @@ -108,7 +109,15 @@ export interface FileSharingOptions {
* @private
*/
export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
const { onFetchAvatarPersonaData, onRenderMessage, onRenderTypingIndicator, options, styles, fileSharing } = props;
const {
onFetchAvatarPersonaData,
onRenderMessage,
onRenderTypingIndicator,
options,
styles,
fileSharing,
formFactor
} = props;

const defaultNumberOfChatMessagesToReload = 5;
/* @conditional-compile-remove(file-sharing) */
Expand Down Expand Up @@ -147,16 +156,19 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
/* @conditional-compile-remove(file-sharing) */
const userId = toFlatCommunicationIdentifier(adapter.getState().userId);

const fileUploadButtonOnChange = (files: FileList | null): void => {
if (!files) {
return;
}
const fileUploadButtonOnChange = useCallback(
(files: FileList | null): void => {
if (!files) {
return;
}

/* @conditional-compile-remove(file-sharing) */
const fileUploads = adapter.registerActiveFileUploads(Array.from(files));
/* @conditional-compile-remove(file-sharing) */
fileSharing?.uploadHandler(userId, fileUploads);
};
/* @conditional-compile-remove(file-sharing) */
const fileUploads = adapter.registerActiveFileUploads(Array.from(files));
/* @conditional-compile-remove(file-sharing) */
fileSharing?.uploadHandler(userId, fileUploads);
},
[adapter, fileSharing, userId]
);

/* @conditional-compile-remove(file-sharing) */
const onRenderFileDownloads = useCallback(
Expand All @@ -173,6 +185,19 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
[fileSharing?.downloadHandler]
);

const AttachFileButton = useCallback(() => {
if (!fileSharing?.uploadHandler) {
return null;
}
return (
<FileUploadButton
accept={fileSharing?.accept}
multiple={fileSharing?.multiple}
onChange={fileUploadButtonOnChange}
/>
);
}, [fileSharing?.accept, fileSharing?.multiple, fileSharing?.uploadHandler, fileUploadButtonOnChange]);

return (
<Stack className={chatContainer} grow>
{options?.topic !== false && <ChatHeader {...headerProps} />}
Expand Down Expand Up @@ -205,23 +230,25 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
<TypingIndicator {...typingIndicatorProps} styles={typingIndicatorStyles} />
)}
</div>
<SendBox
{...sendBoxProps}
autoFocus={options?.autoFocus}
styles={sendBoxStyles}
/* @conditional-compile-remove(file-sharing) */
activeFileUploads={useSelector(fileUploadsSelector).files}
/* @conditional-compile-remove(file-sharing) */
onCancelFileUpload={adapter.cancelFileUpload}
/>

{fileSharing?.uploadHandler && (
<FileUploadButton
accept={fileSharing?.accept}
multiple={fileSharing?.multiple}
onChange={fileUploadButtonOnChange}
/>
)}
<Stack horizontal={formFactor === 'mobile'} horizontalAlign="start">
Comment thread
anjulgarg marked this conversation as resolved.
Outdated
{formFactor === 'mobile' && (
<Stack verticalAlign="center">
<AttachFileButton />
</Stack>
)}
<div style={{ width: '100%' }}>
Comment thread
anjulgarg marked this conversation as resolved.
Outdated
<SendBox
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we bringing any of these features to the sendbox component we export?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure about the best way to do it.
Since the placement of file attach icon is so different for mobile and desktop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah not sure either, but can always make this decision when we support rich text as that's when many more changes will be made cc @PorterNan

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. That's why I am keeping it simple for now.

{...sendBoxProps}
autoFocus={options?.autoFocus}
styles={sendBoxStyles}
/* @conditional-compile-remove(file-sharing) */
activeFileUploads={useSelector(fileUploadsSelector).files}
/* @conditional-compile-remove(file-sharing) */
onCancelFileUpload={adapter.cancelFileUpload}
/>
</div>
{formFactor !== 'mobile' && <AttachFileButton />}
</Stack>
</Stack>
</Stack>
{
Expand Down