diff --git a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js index 5d8c20a600..44a053ad1b 100644 --- a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js +++ b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef } from 'react'; import { css } from '@emotion/react'; import { Box, @@ -17,6 +17,11 @@ import { getChatInputFormattingToolbarStyles } from './ChatInput.styles'; import formatSelection from '../../lib/formatSelection'; import InsertLinkToolBox from './InsertLinkToolBox'; +const MOBILE_BREAKPOINT = 499; + +const isMobileViewport = () => + typeof window !== 'undefined' && window.innerWidth <= MOBILE_BREAKPOINT; + const ChatInputFormattingToolbar = ({ messageRef, inputRef, @@ -58,6 +63,14 @@ const ChatInputFormattingToolbar = ({ formatSelection(messageRef, item.pattern); setPopoverOpen(false); }; + const openEmojiPicker = () => { + if (isMobileViewport()) { + messageRef.current?.blur?.(); + } + + setPopoverOpen(false); + setEmojiOpen(true); + }; const handleEmojiClick = (emojiEvent) => { const [emoji] = emojiEvent.names; const message = `${messageRef.current.value} :${emoji.replace( @@ -92,7 +105,7 @@ const ChatInputFormattingToolbar = ({ disabled={isRecordingMessage} onClick={() => { if (isRecordingMessage) return; - setEmojiOpen(true); + openEmojiPicker(); }} > @@ -106,7 +119,7 @@ const ChatInputFormattingToolbar = ({ disabled={isRecordingMessage} onClick={() => { if (isRecordingMessage) return; - setEmojiOpen(true); + openEmojiPicker(); }} > @@ -341,6 +354,7 @@ const ChatInputFormattingToolbar = ({ handleEmojiClick(emoji); }} onClose={() => setEmojiOpen(false)} + useMobileBottomSheet={isMobileViewport()} positionStyles={css` position: absolute; bottom: 7rem; diff --git a/packages/react/src/views/EmojiPicker/EmojiPicker.js b/packages/react/src/views/EmojiPicker/EmojiPicker.js index 6501eb8320..ee63acdfd4 100644 --- a/packages/react/src/views/EmojiPicker/EmojiPicker.js +++ b/packages/react/src/views/EmojiPicker/EmojiPicker.js @@ -1,10 +1,15 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import EmojiPicker from 'emoji-picker-react'; import { css } from '@emotion/react'; import PropTypes from 'prop-types'; -import { Box, Popup, useTheme } from '@embeddedchat/ui-elements'; +import { Box, Popup, ReactPortal, useTheme } from '@embeddedchat/ui-elements'; import getEmojiPickerStyles from './EmojiPicker.styles'; +const MOBILE_BREAKPOINT = 499; + +const getIsMobileViewport = () => + typeof window !== 'undefined' && window.innerWidth <= MOBILE_BREAKPOINT; + const CustomEmojiPicker = ({ handleEmojiClick, positionStyles = css` @@ -14,15 +19,55 @@ const CustomEmojiPicker = ({ `, wrapperId = 'emoji-popup', onClose = () => {}, + useMobileBottomSheet = false, }) => { const theme = useTheme(); const styles = getEmojiPickerStyles(theme); + const isMobileBottomSheet = useMobileBottomSheet && getIsMobileViewport(); const previewConfig = { defaultEmoji: '1f60d', defaultCaption: 'None', showPreview: true, }; + useEffect(() => { + if (!isMobileBottomSheet || typeof document === 'undefined') { + return undefined; + } + + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + + return () => { + document.body.style.overflow = previousOverflow; + }; + }, [isMobileBottomSheet]); + + if (isMobileBottomSheet) { + return ( + + + event.stopPropagation()} + > + + + + + + ); + } + return ( { background: ${theme.colors.primary}; } `, + mobileSheetBackdrop: css` + position: fixed; + inset: 0; + z-index: ${(theme.zIndex?.modal || 1500) - 1}; + background: ${alpha(theme.colors.foreground, 0.08)}; + `, + mobileSheet: css` + position: fixed; + left: 0.5rem; + right: 0.5rem; + bottom: 0.5rem; + z-index: ${theme.zIndex?.modal || 1500}; + display: flex; + flex-direction: column; + height: min(28rem, calc(100vh - 4rem)); + max-height: min(28rem, calc(100vh - 4rem)); + overflow: hidden; + border: 1px solid ${theme.colors.border}; + border-radius: ${theme.radius}; + background: ${theme.colors.background}; + box-shadow: ${theme.shadows[2]}; + padding-bottom: env(safe-area-inset-bottom, 0); + + @supports (height: 100dvh) { + height: min(28rem, calc(100dvh - 4rem)); + max-height: min(28rem, calc(100dvh - 4rem)); + } + `, + mobileEmojiPicker: css` + height: 100%; + + .EmojiPickerReact { + width: 100% !important; + height: 100% !important; + border: none; + border-radius: inherit; + } + `, }; return styles;