UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

327 lines 13 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { mergeStyles } from '@fluentui/react'; import { makeStyles, shorthands, tokens } from '@fluentui/react-components'; import { MESSAGE_STATUS_INDICATOR_SIZE_REM } from './MessageStatusIndicator.styles'; import { _ATTACHMENT_CARD_MARGIN_IN_PX, _ATTACHMENT_CARD_WIDTH_IN_REM } from './AttachmentCard.styles'; import { BROKEN_IMAGE_SVG_DATA } from './Common.style'; // Minimum chat bubble width. This matches the minimum chat bubble width from FluentUI // that can contain a message and a timestamp. const CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM = 6.25; // Chat messages should span just short of the width of the container. // When calculating the width of a message we also must take into account // the width of the avatar/gutter and the gap between the message and avatar/gutter. const AVATAR_WIDTH_REM = 2; const AVATAR_MARGIN_LEFT = 2.5; const AVATAR_MESSAGE_GAP_REM = 0.125; const MESSAGE_AMOUNT_OUT_FROM_EDGE_REM = 2; // Avatars should display on top of chat messages when the chat thread is narrow const MESSAGE_AVATAR_OVERLAP_REM = 0.925; const CHAT_MESSAGE_ZINDEX = 1; const AVATAR_ZINDEX = 2; // new message button should be on top of chat message const NEW_MESSAGE_BUTTON_ZINDEX = 2; /** * @private */ export const chatMyMessageActionMenuClassName = 'ChatMyMessage__actions'; /** * @private */ export const messageThreadContainerStyle = mergeStyles({ height: '100%', width: '100%', maxHeight: '100%', overflow: 'auto', position: 'relative', alignSelf: 'center' }); /** * @private */ export const messageThreadWrapperContainerStyle = mergeStyles({ height: '100%', width: '100%', position: 'relative' }); /** * @private */ export const noMessageStatusStyle = mergeStyles({ // This should match the size of the message status indicator icon to ensure // multiple messages sent by the user are aligned correctly. width: `${MESSAGE_STATUS_INDICATOR_SIZE_REM}rem` }); /** * @private */ export const useChatStyles = makeStyles({ root: Object.assign(Object.assign(Object.assign({ // chat components sets max width value to 1056px, override it to 100% maxWidth: '100%', paddingTop: '0.8rem', paddingBottom: '0.5rem', paddingRight: '0.6rem', paddingLeft: '0.6rem' }, shorthands.border('none')), shorthands.overflow('auto')), { // `height: 100%` ensures that the Chat component covers 100% of it's parents height // to prevent intermittent scrollbars when GIFs are present in the chat. height: '100%', '& a:link': { color: tokens.colorBrandForegroundLink }, '& a:visited': { color: tokens.colorBrandForegroundLinkHover }, '& a:hover': { color: tokens.colorBrandForegroundLinkHover } }) }); /** * @private */ export const useChatMessageRenderStyles = makeStyles({ rootCommon: {}, rootMessage: Object.assign(Object.assign(Object.assign({}, shorthands.padding('0')), shorthands.margin('0')), { maxWidth: '100%', minWidth: `${CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM}rem` }), rootMyMessage: Object.assign(Object.assign({ gridTemplateColumns: 'auto fit-content(0)', gridTemplateAreas: ` "body status" `, columnGap: '0', gridGap: '0' }, shorthands.padding('0')), { marginTop: '0', marginRight: '0', marginBottom: '0', marginLeft: '50px', width: `calc(100% - 50px)` }), bodyCommon: Object.assign(Object.assign({}, shorthands.padding('0')), { marginRight: '0', marginBottom: '0', backgroundColor: 'transparent', maxWidth: '100%', minWidth: `${CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM}rem` }), bodyMyMessage: { width: '100%', marginTop: '0' }, bodyWithoutAvatar: { marginLeft: `${AVATAR_MARGIN_LEFT}rem`, marginTop: '0' }, bodyHiddenAvatar: { marginLeft: 0, marginTop: 0 }, bodyWithAvatar: { marginLeft: `0`, marginTop: '0.75rem' }, avatarNoOverlap: { width: `calc(100% - ${AVATAR_WIDTH_REM + MESSAGE_AMOUNT_OUT_FROM_EDGE_REM + AVATAR_MESSAGE_GAP_REM}rem)` }, avatarOverlap: { width: `calc(100% - ${AVATAR_WIDTH_REM + MESSAGE_AMOUNT_OUT_FROM_EDGE_REM - MESSAGE_AVATAR_OVERLAP_REM}rem)` } }); /** * @private */ export const useChatMyMessageStyles = makeStyles({ root: { gridTemplateColumns: 'auto', gridTemplateAreas: ` "body" `, gridGap: '0', paddingTop: '0', marginLeft: '0' }, body: Object.assign(Object.assign({ paddingBottom: '10px', marginTop: '1rem', maxWidth: '100%', minWidth: `${CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM}rem`, marginLeft: '0rem' }, shorthands.border('1px', 'solid', 'transparent')), { '@media (hover: hover)': { '&:hover .ChatMyMessage__actions': { visibility: 'visible' } }, '&:focus .ChatMyMessage__actions': { visibility: 'visible' }, '& msft-mention': { color: tokens.colorStatusWarningBackground3, fontWeight: 600 }, '& img': { maxWidth: '100%', height: 'auto' } }), /* @conditional-compile-remove(rich-text-editor-image-upload) */ bodyWithPlaceholderImage: { // Adding width and height to the placeholder image only for myMessages // because inline images sent from ACS doesn't have width and height in the image tag '& img[src=""]': { width: '12rem', height: '12rem' } }, bodyAttached: { marginTop: '0.125rem' }, menu: Object.assign(Object.assign({ boxShadow: tokens.shadow4, backgroundColor: tokens.colorNeutralBackground1, position: 'absolute', top: '-19px', right: '0' }, shorthands.borderRadius(tokens.borderRadiusMedium)), { // Ensure the focus border around the message bubble doesn't overlap on top of more options button zIndex: 2, lineHeight: tokens.lineHeightBase100, visibility: 'hidden', '&:hover, &:focus': { cursor: 'pointer', visibility: 'visible' } }), menuHidden: { visibility: 'hidden' }, menuVisible: { visibility: 'visible' }, multipleAttachmentsInViewing: { width: '100%', maxWidth: `${(_ATTACHMENT_CARD_WIDTH_IN_REM + _ATTACHMENT_CARD_MARGIN_IN_PX) * 2}rem` }, multipleAttachmentsInEditing: { // when in editing state, the chat message width should not be // limited by content length but occupying the full width instead width: '100%', float: 'right' } }); /** * @private */ export const newMessageButtonContainerStyle = mergeStyles({ position: 'absolute', zIndex: NEW_MESSAGE_BUTTON_ZINDEX, bottom: 0, right: '1.5rem' }); /** * @private */ export const chatMessageDateStyle = { fontWeight: 600 }; /** * @private */ export const useChatMessageStyles = makeStyles({ root: { paddingTop: '0' }, body: Object.assign(Object.assign({ maxWidth: '100%', minWidth: `${CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM}rem`, marginRight: '0rem', paddingBottom: '10px', zIndex: CHAT_MESSAGE_ZINDEX }, shorthands.border('1px', 'solid', 'transparent')), { '& > div:first-of-type': { flexWrap: 'wrap' }, '& msft-mention': { color: tokens.colorStatusWarningBackground3, fontWeight: tokens.fontWeightSemibold }, '& img': { maxWidth: '100% !important', // Add !important to make sure it won't be overridden by style defined in element height: 'auto !important' }, '& video': { maxWidth: '100% !important', // Add !important to make sure it won't be overridden by style defined in element height: 'auto !important' }, '& p': Object.assign({}, shorthands.marginBlock('0.125rem')), '& blockquote': Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ backgroundColor: tokens.colorBrandBackgroundInverted, clear: 'left', minHeight: '2.25rem', width: 'fit-content', marginTop: '7px', marginRight: '0px', marginLeft: '0px', marginBottom: '7px', paddingTop: '7px', paddingRight: '15px', paddingLeft: '15px', paddingBottom: '7px' }, shorthands.border('solid')), shorthands.borderRadius('4px')), shorthands.borderWidth('1px')), shorthands.borderColor(tokens.colorNeutralStroke1Selected)), { borderLeftWidth: '4px' }), '& code': { whiteSpace: 'pre-wrap' } }), bodyWithoutAvatar: { marginTop: '0.125rem' }, bodyWithAvatar: { marginTop: `0.375rem` }, avatarNoOverlap: { marginLeft: `${-AVATAR_MARGIN_LEFT + AVATAR_MESSAGE_GAP_REM}rem` }, avatarOverlap: { marginLeft: `${-AVATAR_MARGIN_LEFT - MESSAGE_AVATAR_OVERLAP_REM}rem` }, multipleAttachments: { width: `${(_ATTACHMENT_CARD_WIDTH_IN_REM + _ATTACHMENT_CARD_MARGIN_IN_PX) * 2}rem` } }); /** * @private */ export const useChatMessageCommonStyles = makeStyles({ body: { '& table': { backgroundColor: tokens.colorBrandBackgroundInverted, borderCollapse: 'collapse', tableLayout: 'auto', width: '100%', '& tr': Object.assign(Object.assign({}, shorthands.border('1px', 'solid', `${tokens.colorNeutralStrokeAccessible}`)), { '& td': Object.assign(Object.assign({}, shorthands.border('1px', 'solid', `${tokens.colorNeutralStrokeAccessible}`)), { wordBreak: 'normal', paddingTop: '0px', paddingRight: '5px' }) }) } }, bodyWithPlaceholderImage: { '& img[src=""]': { backgroundColor: tokens.colorNeutralBackground1Selected, // this ensures safari won't have default rendering when image source // becomes invalid such as empty string in this case fontSize: '0', position: 'relative', clipPath: 'inset(0.3px)', display: 'flex' }, '& img[src=""]:after': { backgroundColor: tokens.colorNeutralBackground1Selected, content: `url("data:image/gif;base64,R0lGODlhAQABAAAAACw=")`, backgroundSize: 'center', position: 'absolute', width: '100%', height: '100%', top: '0', left: '0', display: 'block' }, '& .broken-image-wrapper': Object.assign(Object.assign({ width: '12rem', height: '12rem', marginTop: '0.75rem', display: 'flex', justifyContent: 'center', alignItems: 'center' }, shorthands.outline('1px', 'solid', tokens.colorNeutralForegroundDisabled)), { backgroundColor: tokens.colorNeutralBackground2 }), '& .broken-image-wrapper:after': { content: `''`, maskImage: `url("${BROKEN_IMAGE_SVG_DATA}");`, width: '3rem', height: '3rem', backgroundColor: `${tokens.colorNeutralForeground2}` } }, failed: { //TODO: can we reuse a theme color here? backgroundColor: 'rgba(168, 0, 0, 0.2)' }, blocked: Object.assign(Object.assign({ maxWidth: '100%', minWidth: `${CHAT_MESSAGE_CONTAINER_MIN_WIDTH_REM}rem`, marginRight: '0rem', color: tokens.colorNeutralForeground2 }, shorthands.border('1px', 'solid', 'transparent')), { '& i': { paddingTop: '0.25rem' }, '& p': Object.assign(Object.assign({}, shorthands.marginBlock('0.125rem')), { paddingRight: '0.75rem', fontStyle: 'italic' }), '& a': Object.assign(Object.assign(Object.assign({}, shorthands.marginBlock('0.125rem')), { fontStyle: 'normal', color: tokens.colorBrandForegroundLink }), shorthands.textDecoration('none')) }) }); /** * @private */ export const gutterWithAvatar = { paddingTop: '1.65rem', width: `${AVATAR_WIDTH_REM}rem`, position: 'relative', float: 'left', display: 'block', visibility: 'visible', zIndex: AVATAR_ZINDEX }; /** * @private */ export const gutterWithHiddenAvatar = Object.assign(Object.assign({}, gutterWithAvatar), { visibility: 'hidden', // we use this hidden avatar just as a width placeholder // the placeholder is needed for responsive bubble width height: 0 }); /** * @private */ export const newMessageButtonStyle = mergeStyles({ float: 'right', width: 'fit-content' }); /** * @private */ export const buttonWithIconStyles = { textContainer: { display: 'contents' } }; /** * @private */ export const loadPreviousMessageButtonStyle = mergeStyles({ border: 'none', minHeight: '1.5rem', '&:hover': { background: 'none' }, '&:active': { background: 'none' } }); /** * @private */ export const DownIconStyle = mergeStyles({ marginRight: '0.5em' }); /** @private */ export const dataLossIconStyle = mergeStyles({ width: '1.25rem', height: '1.25rem' }); /** @private */ export const messageTextContentStyles = mergeStyles({ whiteSpace: 'pre-wrap' }); //# sourceMappingURL=MessageThread.styles.js.map