UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

126 lines (125 loc) 7.67 kB
import { nanoid } from 'nanoid'; import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { UploadIcon as DefaultUploadIcon } from './icons'; import { CHANNEL_CONTAINER_ID } from '../Channel/constants'; import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog'; import { DialogMenuButton } from '../Dialog/DialogMenu'; import { Modal } from '../Modal'; import { PollCreationDialog as DefaultPollCreationDialog } from '../Poll'; import { Portal } from '../Portal/Portal'; import { UploadFileInput } from '../ReactFileUtilities'; import { useChannelStateContext, useComponentContext, useMessageInputContext, useTranslationContext, } from '../../context'; import { AttachmentSelectorContextProvider, useAttachmentSelectorContext, } from '../../context/AttachmentSelectorContext'; export const SimpleAttachmentSelector = () => { const { AttachmentSelectorInitiationButtonContents, FileUploadIcon = DefaultUploadIcon, } = useComponentContext(); const inputRef = useRef(null); const [labelElement, setLabelElement] = useState(null); const id = useMemo(() => nanoid(), []); 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(); return (React.createElement(DialogMenuButton, { className: 'str-chat__attachment-selector-actions-menu__button str-chat__attachment-selector-actions-menu__upload-file-button', onClick: () => { if (fileInput) fileInput.click(); closeMenu(); } }, t('File'))); }, 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', }, ]; const useAttachmentSelectorActionsFiltered = (original) => { const { PollCreationDialog = DefaultPollCreationDialog } = useComponentContext(); const { channelCapabilities, channelConfig } = useChannelStateContext(); const { isThreadInput } = useMessageInputContext(); return original .filter((action) => { if (action.type === 'uploadFile' && !channelCapabilities['upload-file']) return false; if (action.type === 'createPoll' && (!channelConfig?.polls || isThreadInput || !channelCapabilities['send-poll'])) return false; return true; }) .map((action) => { if (action.type === 'createPoll' && !action.ModalContent) { return { ...action, ModalContent: PollCreationDialog }; } return action; }); }; export const AttachmentSelector = ({ attachmentSelectorActionSet = defaultAttachmentSelectorActionSet, getModalPortalDestination, }) => { const { t } = useTranslationContext(); const { channelCapabilities } = useChannelStateContext(); const { isThreadInput } = useMessageInputContext(); const actions = useAttachmentSelectorActionsFiltered(attachmentSelectorActionSet); const menuDialogId = `attachment-actions-menu${isThreadInput ? '-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, 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: 'str-chat__create-poll-modal', onClose: closeModal, open: modalIsOpen }, ModalContent && React.createElement(ModalContent, { close: closeModal })))))); };