From cf71aff62698f43bad9eb356dd7622cf1eb17d39 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Wed, 11 Oct 2023 01:31:06 +0200 Subject: [PATCH] [WIP] Add new EmojiPicker, adjust message inputs --- .../React/release-guides/emoji-picker.mdx | 78 ++++++++++++++ package.json | 11 +- src/components/MessageInput/EmojiPicker.tsx | 100 ++++++++++++++++++ .../MessageInput/MessageInputFlat.tsx | 8 +- .../MessageInput/MessageInputSmall.tsx | 6 +- src/components/MessageInput/icons.tsx | 40 +++++++ src/components/MessageInput/index.ts | 1 + yarn.lock | 26 ++++- 8 files changed, 256 insertions(+), 14 deletions(-) create mode 100644 docusaurus/docs/React/release-guides/emoji-picker.mdx create mode 100644 src/components/MessageInput/EmojiPicker.tsx diff --git a/docusaurus/docs/React/release-guides/emoji-picker.mdx b/docusaurus/docs/React/release-guides/emoji-picker.mdx new file mode 100644 index 0000000000..0b70a26122 --- /dev/null +++ b/docusaurus/docs/React/release-guides/emoji-picker.mdx @@ -0,0 +1,78 @@ +--- +id: new-emoji-picker-integration +sidebar_position: 3 +title: EmojiPicker Integration (11.0.0) +keywords: [migration guide, upgrade, emoji picker, breaking changes, v11] +--- + +import GHComponentLink from '../_docusaurus-components/GHComponentLink'; + +## Dropping support for built-in `EmojiPicker` (with breaking changes) + +By default - our SDK would ship with `emoji-mart` dependency on top of which our `EmojiPicker` component is built. And since the SDK is using `emoji-mart` for this component - it was also reused for reactions (`ReactionsList` and `ReactionSelector`) and suggestion list (`MessageInput`). This solution proved to be very uncomfortable to work with when it came to replacing either of the mentioned components (or disabling them completely) and the final applications using our SDK would still bundle certain `emoji-mart` parts which weren't needed (or seemingly "disabled") resulting in sub-optimal load times. Maintaining such architecture became a burden so we're switching things a bit. + +## Changes to the default component composition (architecture) + +Component now comes as two-part "bundle" - a button and an actual picker element. The component now holds its own `open` state which is handled by the button. + +{/* TODO: extend once you have the component ready */} +{/* TODO: mention that we're dropping the EmojiContext */} + +## Switching to opt-in mechanism (BREAKING CHANGE) + +We made `emoji-mart` package in our SDK completely optional which means that `EmojiPicker` component is now disabled by default. + +### Reinstate the `EmojiPicker` component + +Add `emoji-mart` to your packages and make sure the package versions fit our peer-dependency requirements: + +```bash +yarn add emoji-mart@^5.5.2 @emoji-mart/data@^1.1.2 @emoji-mart/react@^1.1.1 +``` + +\\Import `EmojiPicker` component from the `stream-chat-react` package: + +```tsx +import { Channel, EmojiPicker } from 'stream-chat-react'; + +// and apply it to the Channel (component context) + +const ChannelWrapper = ({ children }) => { + return {children}; +}; +``` + +### Build your custom `EmojiPicker` (example) + +If `emoji-mart` is too heavy for your use-case and you'd like to build your own you can certainly do so, here's a simple `EmojiPicker` built using `emoji-picker-react` package: + +```tsx +import EmojiPicker from 'emoji-picker-react'; +import { useMessageInputContext } from 'stream-chat-react'; + +export const CustomEmojiPicker = () => { + const [open, setOpen] = useState(false); + + const { insertText } = useMessageInputContext(); + + return ( + <> + + + {open && ( + { + event.preventDefault(); // prevents cursor from changing place in the input + insertText(emoji.emoji); + }} + /> + )} + + ); +}; + +// and apply it to the Channel (component context) +``` + +You can make the component slightly better using `FloatingUI` by wrapping the actual picker element to make it float perfectly positioned above the button. See the source of the component which comes with the SDK for inspiration. +{/* TODO: provide link to the source (once ready) */} diff --git a/package.json b/package.json index d1550ce1b9..6f8d9d4aa7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stream-chat-react", - "version": "0.0.0-development", + "version": "0.0.5-development", "description": "React components to create chat conversations or livestream style chat", "author": "GetStream", "homepage": "https://getstream.io/chat/", @@ -67,7 +67,13 @@ "peerDependencies": { "react": "^18.0.0 || ^17.0.0 || ^16.8.0", "react-dom": "^18.0.0 || ^17.0.0 || ^16.8.0", - "stream-chat": "^8.0.0" + "stream-chat": "^8.0.0", + "emoji-picker-react": "^4.5.2" + }, + "peerDependenciesMeta": { + "emoji-picker-react": { + "optional": true + } }, "files": [ "dist", @@ -127,6 +133,7 @@ "codecov": "^3.8.1", "core-js": "^3.6.5", "css-loader": "^5.0.1", + "emoji-picker-react": "^4.5.2", "eslint": "7.14.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^6.15.0", diff --git a/src/components/MessageInput/EmojiPicker.tsx b/src/components/MessageInput/EmojiPicker.tsx new file mode 100644 index 0000000000..19bcbada86 --- /dev/null +++ b/src/components/MessageInput/EmojiPicker.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { usePopper } from 'react-popper'; + +// TODO: replace with emoji-mart +import Picker from 'emoji-picker-react'; + +import { + ThemeVersion, + useChatContext, + useMessageInputContext, + useTranslationContext, +} from '../../context'; +import { EmojiIconLarge, EmojiPickerIcon } from './icons'; +import { Tooltip } from '../Tooltip'; + +const classNames: Record< + ThemeVersion, + Record< + 'buttonClassName' | 'emojiPickerContainerClassName' | 'wrapperClassName', + string | undefined + > +> = { + 1: { + buttonClassName: 'str-chat__input-flat-emojiselect', + emojiPickerContainerClassName: undefined, + wrapperClassName: 'str-chat__emojiselect-wrapper', + }, + 2: { + buttonClassName: 'str-chat__emoji-picker-button', + emojiPickerContainerClassName: 'str-chat__message-textarea-emoji-picker-container', + wrapperClassName: 'str-chat__message-textarea-emoji-picker', + }, +}; + +// TODO: handle small variant (MessageInputSmall) +export const EmojiPicker = () => { + const { themeVersion } = useChatContext('EmojiPicker'); + const { t } = useTranslationContext('EmojiPicker'); + const { insertText, textareaRef } = useMessageInputContext('EmojiPicker'); + const [displayPicker, setDisplayPicker] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const { attributes, styles } = usePopper(referenceElement, popperElement, { + placement: themeVersion === '2' ? 'top-end' : 'top-start', + }); + + const { buttonClassName, emojiPickerContainerClassName, wrapperClassName } = classNames[ + themeVersion + ]; + + useEffect(() => { + if (!popperElement || !referenceElement) return; + + const handlePointerDown = (e: PointerEvent) => { + const target = e.target as HTMLElement; + + if (popperElement.contains(target) || referenceElement.contains(target)) return; + + setDisplayPicker(false); + }; + + window.addEventListener('pointerdown', handlePointerDown); + return () => window.removeEventListener('pointerdown', handlePointerDown); + }, [referenceElement, popperElement]); + + return ( +
+ {displayPicker && ( +
+ { + insertText(e.emoji); + textareaRef.current?.focus(); + }} + /> +
+ )} + {themeVersion === '1' && ( + + {displayPicker ? t('Close emoji picker') : t('Open emoji picker')} + + )} + +
+ ); +}; diff --git a/src/components/MessageInput/MessageInputFlat.tsx b/src/components/MessageInput/MessageInputFlat.tsx index 5a1b041e1b..a386f2b90f 100644 --- a/src/components/MessageInput/MessageInputFlat.tsx +++ b/src/components/MessageInput/MessageInputFlat.tsx @@ -113,12 +113,8 @@ const MessageInputV1 = <
{isUploadEnabled && }
- {EmojiPicker && ( -
- -
- )} - {cooldownRemaining && ( + {EmojiPicker && } + {!!cooldownRemaining && (
)} - {EmojiPicker && ( -
- -
- )} + {EmojiPicker && } )}
diff --git a/src/components/MessageInput/icons.tsx b/src/components/MessageInput/icons.tsx index 5941202265..b3309de471 100644 --- a/src/components/MessageInput/icons.tsx +++ b/src/components/MessageInput/icons.tsx @@ -8,6 +8,46 @@ import type { Message } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../types/types'; +export const EmojiIconLarge = () => { + const { t } = useTranslationContext('EmojiIconLarge'); + + return ( + + {t('Open emoji picker')} + + + + + ); +}; + +export const EmojiIconSmall = () => { + const { t } = useTranslationContext('EmojiIconSmall'); + + return ( + + {t('Open emoji picker')} + + + + + ); +}; + +// ThemingV2 icon +export const EmojiPickerIcon = () => ( + + + + + +); + export const FileUploadIcon = () => { const { t } = useTranslationContext('FileUploadIcon'); diff --git a/src/components/MessageInput/index.ts b/src/components/MessageInput/index.ts index 69849146d2..71fd4b0821 100644 --- a/src/components/MessageInput/index.ts +++ b/src/components/MessageInput/index.ts @@ -10,3 +10,4 @@ export * from './MessageInputSmall'; export * from './QuotedMessagePreview'; export * from './UploadsPreview'; export * from './types'; +export * from './EmojiPicker'; diff --git a/yarn.lock b/yarn.lock index e78a9a682f..b87b2b8342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1132,7 +1132,14 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": +"@babel/runtime@^7.0.0": + version "7.23.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d" + integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -4561,6 +4568,11 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" @@ -5608,6 +5620,13 @@ emoji-mart@3.0.1: "@babel/runtime" "^7.0.0" prop-types "^15.6.0" +emoji-picker-react@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.2.tgz#74317db98099ea0e3d42719768efc7a227f152cd" + integrity sha512-08v+yyFizoR9rEtLjuodTdlYQbkk0QWeKm6er0RQjks+b6Bfpw1UX9XheLcXreEAve7VVE7Tcu/kSAIm54V4zA== + dependencies: + clsx "^1.2.1" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -12354,6 +12373,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.14.2: version "0.14.5" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"