UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

151 lines (150 loc) 9.07 kB
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 })))))); };