communication-react-19
Version:
React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)
126 lines • 7.14 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { Persona, PersonaSize, Stack, mergeStyles, useTheme } from '@fluentui/react';
import { mergeClasses } from '@fluentui/react-components';
import { mentionPopoverContainerStyle, headerStyleThemed, suggestionItemStackStyle, suggestionItemWrapperStyle, useSuggestionListStyle } from './styles/MentionPopover.style';
/* @conditional-compile-remove(mention) */
import { useIdentifiers } from '../identifiers';
import { useLocale } from '../localization';
import { useDefaultStackStyles } from './styles/Stack.style';
/**
* Component to render a pop-up of mention suggestions.
*
* @internal
*/
export const _MentionPopover = (props) => {
const { suggestions, activeSuggestionIndex, title, target, targetPositionOffset, onRenderSuggestionItem, onSuggestionSelected, onDismiss, location } = props;
const theme = useTheme();
/* @conditional-compile-remove(mention) */
const ids = useIdentifiers();
const localeStrings = useLocale().strings;
const popoverRef = useRef();
const suggestionsListRef = useRef(null);
const [position, setPosition] = useState();
const [hoveredSuggestion, setHoveredSuggestion] = useState(undefined);
const suggestionListStyle = useSuggestionListStyle();
const defaultStackStyles = useDefaultStackStyles();
const dismissPopoverWhenClickingOutside = useCallback((e) => {
const target = e.target;
if (popoverRef.current && !popoverRef.current.contains(target)) {
onDismiss && onDismiss();
}
}, [onDismiss]);
useEffect(() => {
if (suggestionsListRef.current && activeSuggestionIndex !== undefined && activeSuggestionIndex >= 0) {
const selectedItem = suggestionsListRef.current.children[activeSuggestionIndex];
if (selectedItem) {
selectedItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
}, [activeSuggestionIndex]);
useEffect(() => {
window && window.addEventListener('click', dismissPopoverWhenClickingOutside);
return () => {
window && window.removeEventListener('click', dismissPopoverWhenClickingOutside);
};
}, [dismissPopoverWhenClickingOutside]);
// Determine popover position
useEffect(() => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const rect = (_a = target === null || target === void 0 ? void 0 : target.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
const maxWidth = 200;
const finalPosition = { maxWidth };
// Figure out whether it will fit horizontally
const leftOffset = (_b = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.left) !== null && _b !== void 0 ? _b : 0;
if (leftOffset + maxWidth > ((_c = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _c !== void 0 ? _c : 0)) {
finalPosition.right = ((_d = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _d !== void 0 ? _d : 0) - leftOffset;
}
else {
finalPosition.left = leftOffset;
}
// Offset between cursor and mention popover
const verticalOffset = 4;
if (location === 'below') {
finalPosition.top = ((_e = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _e !== void 0 ? _e : 0) + ((_f = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _f !== void 0 ? _f : 0) + verticalOffset;
}
else {
// (location === 'above')
finalPosition.bottom = ((_g = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _g !== void 0 ? _g : 0) - ((_h = targetPositionOffset === null || targetPositionOffset === void 0 ? void 0 : targetPositionOffset.top) !== null && _h !== void 0 ? _h : 0) + verticalOffset;
}
setPosition(finalPosition);
}, [location, target, targetPositionOffset]);
const handleOnKeyDown = useCallback((e) => {
switch (e.key) {
case 'Escape':
onDismiss && onDismiss();
break;
default:
break;
}
}, [onDismiss]);
const personaRenderer = useCallback((displayName) => {
const avatarOptions = {
text: displayName.trim(),
size: PersonaSize.size24,
initialsColor: theme.palette.neutralLight,
initialsTextColor: theme.palette.neutralSecondary,
showOverflowTooltip: false,
showUnknownPersonaCoin: false
};
return React.createElement(Persona, Object.assign({}, avatarOptions));
}, [theme]);
const defaultOnRenderSuggestionItem = useCallback((suggestion, onSuggestionSelected, active) => {
return (React.createElement("div", { "data-is-focusable": true, "data-ui-id": ids.mentionSuggestionItem, key: suggestion.id, onClick: () => onSuggestionSelected(suggestion), onMouseEnter: () => setHoveredSuggestion(suggestion), onMouseLeave: () => setHoveredSuggestion(undefined), onKeyDown: (e) => {
handleOnKeyDown(e);
}, className: suggestionItemWrapperStyle(theme) },
React.createElement(Stack, { horizontal: true, className: suggestionItemStackStyle(theme, (hoveredSuggestion === null || hoveredSuggestion === void 0 ? void 0 : hoveredSuggestion.id) === suggestion.id, active) }, personaRenderer(suggestion.displayText))));
}, [
handleOnKeyDown,
theme,
/* @conditional-compile-remove(mention) */
ids,
hoveredSuggestion,
personaRenderer
]);
const getHeaderTitle = useCallback(() => {
if (title) {
return title;
}
/* @conditional-compile-remove(mention) */
return localeStrings.mentionPopover.mentionPopoverHeader;
return '';
}, [localeStrings, title]);
return (React.createElement("div", { ref: popoverRef }, position && (React.createElement(Stack, { "data-testid": 'mention-suggestion-list-container', className: mergeStyles({
maxHeight: 212,
maxWidth: position.maxWidth
}, mentionPopoverContainerStyle(theme), Object.assign(Object.assign({}, position), { position: 'absolute' })) },
React.createElement(Stack.Item, { styles: headerStyleThemed(theme), "aria-label": title }, getHeaderTitle()),
React.createElement("div", { className: mergeClasses(defaultStackStyles.root, suggestionListStyle.root), "data-ui-id": ids.mentionSuggestionList, ref: suggestionsListRef }, suggestions.map((suggestion, index) => {
const active = index === activeSuggestionIndex;
return onRenderSuggestionItem
? onRenderSuggestionItem(suggestion, onSuggestionSelected, active)
: defaultOnRenderSuggestionItem(suggestion, onSuggestionSelected, active);
}))))));
};
//# sourceMappingURL=MentionPopover.js.map