stream-chat-react
Version:
React components to create chat conversations or livestream style chat
89 lines (88 loc) • 3.98 kB
JavaScript
import clsx from 'clsx';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDialogIsOpen, useDialogOnNearestManager } from './hooks';
import { useDialogAnchor } from './DialogAnchor';
export const ButtonWithSubmenu = ({ children, className, placement, Submenu, submenuContainerProps, ...buttonProps }) => {
const buttonRef = useRef(null);
const [dialogContainer, setDialogContainer] = useState(null);
const keepSubmenuOpen = useRef(false);
const dialogCloseTimeout = useRef(null);
const dialogId = useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId });
const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id);
const { setPopperElement, styles } = useDialogAnchor({
open: dialogIsOpen,
placement,
referenceElement: buttonRef.current,
});
const closeDialogLazily = useCallback(() => {
if (dialogCloseTimeout.current)
clearTimeout(dialogCloseTimeout.current);
dialogCloseTimeout.current = setTimeout(() => {
if (keepSubmenuOpen.current)
return;
dialog.close();
}, 100);
}, [dialog]);
const handleClose = useCallback((event) => {
const parentButton = buttonRef.current;
if (!dialogIsOpen || !parentButton)
return;
event.stopPropagation();
closeDialogLazily();
parentButton.focus();
}, [closeDialogLazily, dialogIsOpen, buttonRef]);
const handleFocusParentButton = () => {
if (dialogIsOpen)
return;
dialog.open();
keepSubmenuOpen.current = true;
};
useEffect(() => {
const parentButton = buttonRef.current;
if (!dialogIsOpen || !parentButton)
return;
const hideOnEscape = (event) => {
if (event.key !== 'Escape')
return;
handleClose(event);
keepSubmenuOpen.current = false;
};
document.addEventListener('keyup', hideOnEscape, { capture: true });
return () => {
document.removeEventListener('keyup', hideOnEscape, { capture: true });
};
}, [dialogIsOpen, handleClose]);
return (React.createElement(React.Fragment, null,
React.createElement("button", { "aria-selected": 'false', className: clsx(className, 'str_chat__button-with-submenu', {
'str_chat__button-with-submenu--submenu-open': dialogIsOpen,
}), onBlur: () => {
keepSubmenuOpen.current = false;
closeDialogLazily();
}, onClick: (event) => {
event.stopPropagation();
dialog.toggle();
}, onFocus: handleFocusParentButton, onMouseEnter: handleFocusParentButton, onMouseLeave: () => {
keepSubmenuOpen.current = false;
closeDialogLazily();
}, ref: buttonRef, role: 'option', ...buttonProps }, children),
dialogIsOpen && (React.createElement("div", { onBlur: (event) => {
const isBlurredDescendant = event.relatedTarget instanceof Node &&
dialogContainer?.contains(event.relatedTarget);
if (isBlurredDescendant)
return;
keepSubmenuOpen.current = false;
closeDialogLazily();
}, onFocus: () => {
keepSubmenuOpen.current = true;
}, onMouseEnter: () => {
keepSubmenuOpen.current = true;
}, onMouseLeave: () => {
keepSubmenuOpen.current = false;
closeDialogLazily();
}, ref: (element) => {
setPopperElement(element);
setDialogContainer(element);
}, style: styles, tabIndex: -1, ...submenuContainerProps },
React.createElement(Submenu, null)))));
};