stream-chat-react
Version:
React components to create chat conversations or livestream style chat
151 lines (150 loc) • 9.07 kB
JavaScript
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { UploadIcon as DefaultUploadIcon } from './icons';
import { useAttachmentManagerState } from './hooks/useAttachmentManagerState';
import { CHANNEL_CONTAINER_ID } from '../Channel/constants';
import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog';
import { DialogMenuButton } from '../Dialog/DialogMenu';
import { Modal as DefaultModal } from '../Modal';
import { ShareLocationDialog as DefaultLocationDialog } from '../Location';
import { PollCreationDialog as DefaultPollCreationDialog } from '../Poll';
import { Portal } from '../Portal/Portal';
import { UploadFileInput } from '../ReactFileUtilities';
import { useChannelStateContext, useComponentContext, useTranslationContext, } from '../../context';
import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext';
import { useStableId } from '../UtilityComponents/useStableId';
import clsx from 'clsx';
import { useMessageComposer } from './hooks';
export const SimpleAttachmentSelector = () => {
const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext();
const inputRef = useRef(null);
const [labelElement, setLabelElement] = useState(null);
const id = useStableId();
useEffect(() => {
if (!labelElement)
return;
const handleKeyUp = (event) => {
if (![' ', 'Enter'].includes(event.key) || !inputRef.current)
return;
event.preventDefault();
inputRef.current.click();
};
labelElement.addEventListener('keyup', handleKeyUp);
return () => {
labelElement.removeEventListener('keyup', handleKeyUp);
};
}, [labelElement]);
return (React.createElement("div", { className: 'str-chat__file-input-container', "data-testid": 'file-upload-button' },
React.createElement(UploadFileInput, { id: id, ref: inputRef }),
React.createElement("label", { className: 'str-chat__file-input-label', htmlFor: id, ref: setLabelElement, tabIndex: 0 }, AttachmentSelectorInitiationButtonContents ? (React.createElement(AttachmentSelectorInitiationButtonContents, null)) : (React.createElement(FileUploadIcon, null)))));
};
const AttachmentSelectorMenuInitButtonIcon = () => {
const { AttachmentSelectorInitiationButtonContents, FileUploadIcon } = useComponentContext('SimpleAttachmentSelector');
if (AttachmentSelectorInitiationButtonContents) {
return React.createElement(AttachmentSelectorInitiationButtonContents, null);
}
if (FileUploadIcon) {
return React.createElement(FileUploadIcon, null);
}
return React.createElement("div", { className: 'str-chat__attachment-selector__menu-button__icon' });
};
export const DefaultAttachmentSelectorComponents = {
File({ closeMenu }) {
const { t } = useTranslationContext();
const { fileInput } = useAttachmentSelectorContext();
const { isUploadEnabled } = useAttachmentManagerState();
return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__upload-file-button', disabled: !isUploadEnabled, onClick: () => {
if (fileInput)
fileInput.click();
closeMenu();
} }, t('File')));
},
Location({ closeMenu, openModalForAction }) {
const { t } = useTranslationContext();
return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__add-location-button', onClick: () => {
openModalForAction('addLocation');
closeMenu();
} }, t('Location')));
},
Poll({ closeMenu, openModalForAction }) {
const { t } = useTranslationContext();
return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__create-poll-button', onClick: () => {
openModalForAction('createPoll');
closeMenu();
} }, t('Poll')));
},
};
export const defaultAttachmentSelectorActionSet = [
{ ActionButton: DefaultAttachmentSelectorComponents.File, type: 'uploadFile' },
{
ActionButton: DefaultAttachmentSelectorComponents.Poll,
type: 'createPoll',
},
{
ActionButton: DefaultAttachmentSelectorComponents.Location,
type: 'addLocation',
},
];
const useAttachmentSelectorActionsFiltered = (original) => {
const { PollCreationDialog = DefaultPollCreationDialog, ShareLocationDialog = DefaultLocationDialog, } = useComponentContext();
const { channelCapabilities } = useChannelStateContext();
const messageComposer = useMessageComposer();
return original
.filter((action) => {
if (action.type === 'uploadFile')
return channelCapabilities['upload-file'];
if (action.type === 'createPoll')
return channelCapabilities['send-poll'] && !messageComposer.threadId;
if (action.type === 'addLocation') {
return messageComposer.config.location.enabled && !messageComposer.threadId;
}
return true;
})
.map((action) => {
if (action.type === 'createPoll' && !action.ModalContent) {
return { ...action, ModalContent: PollCreationDialog };
}
if (action.type === 'addLocation' && !action.ModalContent) {
return { ...action, ModalContent: ShareLocationDialog };
}
return action;
});
};
export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttachmentSelectorActionSet, getModalPortalDestination, }) => {
const { t } = useTranslationContext();
const { Modal = DefaultModal } = useComponentContext();
const { channelCapabilities } = useChannelStateContext();
const messageComposer = useMessageComposer();
const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet);
const menuDialogId = `attachment-actions-menu${messageComposer.threadId ? '-thread' : ''}`;
const menuDialog = useDialog({ id: menuDialogId });
const menuDialogIsOpen = useDialogIsOpen(menuDialogId);
const [modalContentAction, setModalContentActionAction] = useState();
const openModal = useCallback((actionType) => {
const action = actions.find((a) => a.type === actionType);
if (!action?.ModalContent)
return;
setModalContentActionAction(action);
}, [actions]);
const closeModal = useCallback(() => setModalContentActionAction(undefined), []);
const [fileInput, setFileInput] = useState(null);
const menuButtonRef = useRef(null);
const getDefaultPortalDestination = useCallback(() => document.getElementById(CHANNEL_CONTAINER_ID), []);
if (actions.length === 0)
return null;
if (actions.length === 1 && actions[0].type === 'uploadFile')
return React.createElement(SimpleAttachmentSelector, null);
const ModalContent = modalContentAction?.ModalContent;
const modalIsOpen = !!ModalContent;
return (React.createElement(AttachmentSelectorContextProvider, { value: { fileInput } },
React.createElement("div", { className: 'str-chat__attachment-selector' },
channelCapabilities['upload-file'] && React.createElement(UploadFileInput, { ref: setFileInput }),
React.createElement("button", { "aria-expanded": menuDialogIsOpen, "aria-haspopup": 'true', "aria-label": t('aria/Open Attachment Selector'), className: 'str-chat__attachment-selector__menu-button', "data-testid": 'invoke-attachment-selector-button', onClick: () => menuDialog?.toggle(), ref: menuButtonRef },
React.createElement(AttachmentSelectorMenuInitButtonIcon, null)),
React.createElement(DialogAnchor, { id: menuDialogId, placement: 'top-start', referenceElement: menuButtonRef.current, tabIndex: -1, trapFocus: true },
React.createElement("div", { className: 'str-chat__attachment-selector-actions-menu str-chat__dialog-menu', "data-testid": 'attachment-selector-actions-menu' }, actions.map(({ ActionButton, type }) => (React.createElement(ActionButton, { closeMenu: menuDialog.close, key: `attachment-selector-item-${type}`, openModalForAction: openModal }))))),
React.createElement(Portal, { getPortalDestination: getModalPortalDestination ?? getDefaultPortalDestination, isOpen: modalIsOpen },
React.createElement(Modal, { className: clsx({
'str-chat__create-poll-modal': modalContentAction?.type === 'createPoll',
'str-chat__share-location-modal': modalContentAction?.type === 'addLocation',
}), onClose: closeModal, open: modalIsOpen }, ModalContent && React.createElement(ModalContent, { close: closeModal }))))));
};