From fe3c189f00c6942cae59cae8767298786bc325f1 Mon Sep 17 00:00:00 2001 From: Maria Rushkova <66270386+mrushkova@users.noreply.github.com> Date: Mon, 10 Jan 2022 13:27:48 +0100 Subject: [PATCH] Emoji reactions (#176) --- .../src/components/Icons/ReactionIcon.tsx | 13 +-- .../components/Messages/MessageReactions.tsx | 72 +++++++++++++++++ .../src/components/Messages/UiMessage.tsx | 19 +++-- .../components/Reactions/ReactionButton.tsx | 81 +++++++++++++++++++ .../{Form => Reactions}/ReactionPicker.tsx | 52 +++++++++--- .../{Form => Reactions}/Reactions.tsx | 44 +++------- packages/react-chat/src/styles/themes.ts | 6 ++ 7 files changed, 234 insertions(+), 53 deletions(-) create mode 100644 packages/react-chat/src/components/Messages/MessageReactions.tsx create mode 100644 packages/react-chat/src/components/Reactions/ReactionButton.tsx rename packages/react-chat/src/components/{Form => Reactions}/ReactionPicker.tsx (53%) rename packages/react-chat/src/components/{Form => Reactions}/Reactions.tsx (57%) diff --git a/packages/react-chat/src/components/Icons/ReactionIcon.tsx b/packages/react-chat/src/components/Icons/ReactionIcon.tsx index 6e3b376..aed6150 100644 --- a/packages/react-chat/src/components/Icons/ReactionIcon.tsx +++ b/packages/react-chat/src/components/Icons/ReactionIcon.tsx @@ -2,16 +2,12 @@ import React from "react"; import styled from "styled-components"; type ReactionProps = { - width: number; - height: number; className?: string; }; -export function ReactionSvg({ width, height, className }: ReactionProps) { +export function ReactionSvg({ className }: ReactionProps) { return ( theme.secondary}; &:hover { fill: ${({ theme }) => theme.tertiary}; } + + &.small { + width: 18px; + height: 18px; + } `; diff --git a/packages/react-chat/src/components/Messages/MessageReactions.tsx b/packages/react-chat/src/components/Messages/MessageReactions.tsx new file mode 100644 index 0000000..a594262 --- /dev/null +++ b/packages/react-chat/src/components/Messages/MessageReactions.tsx @@ -0,0 +1,72 @@ +import { BaseEmoji, Emoji } from "emoji-mart"; +import React from "react"; +import styled from "styled-components"; + +import { ReactionButton } from "../Reactions/ReactionButton"; + +interface MessageReactionsProps { + messageReactions: BaseEmoji[]; + setMessageReactions: React.Dispatch>; +} + +export function MessageReactions({ + messageReactions, + setMessageReactions, +}: MessageReactionsProps) { + const isMyReactionIncluded = (emoji: BaseEmoji) => + messageReactions.includes(emoji); // temporary function while message reactions are not added to waku + + return ( + + {messageReactions.map((reaction) => ( + + +

1

+
+ ))} + +
+ ); +} + +export const Reactions = styled.div` + display: flex; + align-items: center; + margin-top: 6px; +`; + +const EmojiReaction = styled.button` + display: flex; + align-items: center; + background: rgba(0, 0, 0, 0.05); + border-radius: 2px 10px 10px 10px; + color: ${({ theme }) => theme.primary}; + font-size: 12px; + line-height: 16px; + padding: 2px 8px 2px 2px; + margin-right: 4px; + + &:hover { + background: rgba(0, 0, 0, 0.1); + } + + & > p { + margin-left: 4px; + } + + & > span { + height: 16px; + } + + &.chosen { + background: ${({ theme }) => theme.reactionChosen}; + border: 1px solid ${({ theme }) => theme.tertiary}; + color: ${({ theme }) => theme.tertiary}; + } +`; diff --git a/packages/react-chat/src/components/Messages/UiMessage.tsx b/packages/react-chat/src/components/Messages/UiMessage.tsx index a134740..a22da0f 100644 --- a/packages/react-chat/src/components/Messages/UiMessage.tsx +++ b/packages/react-chat/src/components/Messages/UiMessage.tsx @@ -1,3 +1,4 @@ +import { BaseEmoji } from "emoji-mart"; import React, { useEffect, useMemo, useState } from "react"; import { utils } from "status-communities/dist/cjs"; import styled from "styled-components"; @@ -11,13 +12,13 @@ import { ChatMessage } from "../../models/ChatMessage"; import { equalDate } from "../../utils"; import { ChatMessageContent } from "../Chat/ChatMessageContent"; import { ContactMenu } from "../Form/ContactMenu"; -import { ReactionPicker } from "../Form/ReactionPicker"; -import { Reactions } from "../Form/Reactions"; import { Icon } from "../Icons/Icon"; import { UntrustworthIcon } from "../Icons/UntrustworthIcon"; import { UserLogo } from "../Members/UserLogo"; +import { Reactions } from "../Reactions/Reactions"; import { MessageQuote } from "./MessageQuote"; +import { MessageReactions } from "./MessageReactions"; import { ContentWrapper, DateSeparator, @@ -62,8 +63,9 @@ export function UiMessage({ [message.sender, contacts] ); const [showMenu, setShowMenu] = useState(false); - const [showReactions, setShowReactions] = useState(false); + const [mentioned, setMentioned] = useState(false); + const [messageReactions, setMessageReactions] = useState([]); useEffect(() => { if (mentioned) @@ -146,14 +148,19 @@ export function UiMessage({ setMentioned={setMentioned} /> + {messageReactions.length > 0 && ( + + )} - {showReactions && } diff --git a/packages/react-chat/src/components/Reactions/ReactionButton.tsx b/packages/react-chat/src/components/Reactions/ReactionButton.tsx new file mode 100644 index 0000000..e91641a --- /dev/null +++ b/packages/react-chat/src/components/Reactions/ReactionButton.tsx @@ -0,0 +1,81 @@ +import { BaseEmoji } from "emoji-mart"; +import React, { useRef, useState } from "react"; +import styled from "styled-components"; + +import { useClickOutside } from "../../hooks/useClickOutside"; +import { Tooltip } from "../Form/Tooltip"; +import { ReactionSvg } from "../Icons/ReactionIcon"; + +import { ReactionPicker } from "./ReactionPicker"; + +interface ReactionButtonProps { + className?: string; + messageReactions: BaseEmoji[]; + setMessageReactions: React.Dispatch>; +} + +export function ReactionButton({ + className, + messageReactions, + setMessageReactions, +}: ReactionButtonProps) { + const ref = useRef(null); + useClickOutside(ref, () => setShowReactions(false)); + + const [showReactions, setShowReactions] = useState(false); + + return ( + + {showReactions && ( + + )} + setShowReactions(!showReactions)} + className={className} + > + + {!className && !showReactions && } + + + ); +} + +const Wrapper = styled.div` + position: relative; +`; + +export const ReactionBtn = styled.button` + width: 32px; + height: 32px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + align-self: center; + + &:hover { + background: ${({ theme }) => theme.buttonBgHover}; + } + + &:hover > svg { + fill: ${({ theme }) => theme.tertiary}; + } + + &:hover > div { + visibility: visible; + } + + &.small { + width: 18px; + height: 18px; + padding: 0; + + &:hover { + background: inherit; + } + } +`; diff --git a/packages/react-chat/src/components/Form/ReactionPicker.tsx b/packages/react-chat/src/components/Reactions/ReactionPicker.tsx similarity index 53% rename from packages/react-chat/src/components/Form/ReactionPicker.tsx rename to packages/react-chat/src/components/Reactions/ReactionPicker.tsx index 9900420..a2f25a0 100644 --- a/packages/react-chat/src/components/Form/ReactionPicker.tsx +++ b/packages/react-chat/src/components/Reactions/ReactionPicker.tsx @@ -1,6 +1,6 @@ -import { Emoji, getEmojiDataFromNative } from "emoji-mart"; +import { BaseEmoji, Emoji, getEmojiDataFromNative } from "emoji-mart"; import data from "emoji-mart/data/all.json"; -import React from "react"; +import React, { useCallback } from "react"; import styled from "styled-components"; const emojiHeart = getEmojiDataFromNative("❤️", "twitter", data); @@ -19,11 +19,34 @@ const emojiArr = [ emojiRage, ]; -export function ReactionPicker() { +interface ReactionPickerProps { + className?: string; + messageReactions: BaseEmoji[]; + setMessageReactions: React.Dispatch>; +} + +export function ReactionPicker({ + className, + messageReactions, + setMessageReactions, +}: ReactionPickerProps) { + const handleReaction = useCallback( + (emoji: BaseEmoji) => { + messageReactions.find((e) => e === emoji) + ? setMessageReactions((prev) => prev.filter((e) => e != emoji)) + : setMessageReactions((prev) => [...prev, emoji]); + }, + [messageReactions] + ); + return ( - + {emojiArr.map((emoji) => ( - + handleReaction(emoji)} + className={`${messageReactions.includes(emoji) && "chosen"}`} + > {" "} theme.bodyBackgroundColor}; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08); border-radius: 16px 16px 4px 16px; - padding: 8px 12px; - visibility: hidden; + padding: 8px; + + &.small { + right: unset; + left: -100px; + transform: none; + border-radius: 16px 16px 16px 4px; + } `; const EmojiBtn = styled.button` @@ -62,4 +91,9 @@ const EmojiBtn = styled.button` &:hover { background: ${({ theme }) => theme.inputColor}; } + + &.chosen { + background: ${({ theme }) => theme.reactionBg}; + border: 1px solid ${({ theme }) => theme.tertiary}; + } `; diff --git a/packages/react-chat/src/components/Form/Reactions.tsx b/packages/react-chat/src/components/Reactions/Reactions.tsx similarity index 57% rename from packages/react-chat/src/components/Form/Reactions.tsx rename to packages/react-chat/src/components/Reactions/Reactions.tsx index 21d3bdf..83c4c4c 100644 --- a/packages/react-chat/src/components/Form/Reactions.tsx +++ b/packages/react-chat/src/components/Reactions/Reactions.tsx @@ -1,32 +1,33 @@ +import { BaseEmoji } from "emoji-mart"; import React from "react"; import styled from "styled-components"; import { Reply } from "../../hooks/useReply"; import { ChatMessage } from "../../models/ChatMessage"; -import { ReactionSvg } from "../Icons/ReactionIcon"; +import { Tooltip } from "../Form/Tooltip"; import { ReplySvg } from "../Icons/ReplyIcon"; -import { Tooltip } from "./Tooltip"; +import { ReactionBtn, ReactionButton } from "./ReactionButton"; interface ReactionsProps { message: ChatMessage; - showReactions: boolean; - setShowReactions: (val: boolean) => void; setReply: (val: Reply | undefined) => void; + messageReactions: BaseEmoji[]; + setMessageReactions: React.Dispatch>; } export function Reactions({ message, - showReactions, - setShowReactions, setReply, + messageReactions, + setMessageReactions, }: ReactionsProps) { return ( - setShowReactions(!showReactions)}> - - - + setReply({ @@ -55,26 +56,3 @@ const Wrapper = styled.div` padding: 2px; visibility: hidden; `; - -const ReactionBtn = styled.button` - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 8px; - align-self: center; - position: relative; - - &:hover { - background: ${({ theme }) => theme.buttonBgHover}; - } - - &:hover > svg { - fill: ${({ theme }) => theme.tertiary}; - } - - &:hover > div { - visibility: visible; - } -`; diff --git a/packages/react-chat/src/styles/themes.ts b/packages/react-chat/src/styles/themes.ts index 4183b79..87a82cd 100644 --- a/packages/react-chat/src/styles/themes.ts +++ b/packages/react-chat/src/styles/themes.ts @@ -27,6 +27,8 @@ export type Theme = { mentionBg: string; mentionBgHover: string; shadow: string; + reactionBg: string; + reactionChosen: string; }; export const lightTheme: Theme = { @@ -60,6 +62,8 @@ export const lightTheme: Theme = { mentionBgHover: "#D4F3FA", shadow: "0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)", + reactionBg: "#eceffc", + reactionChosen: "rgba(67, 96, 223, 0.1)", }; export const darkTheme: Theme = { @@ -93,6 +97,8 @@ export const darkTheme: Theme = { mentionBgHover: "#002D39", shadow: "0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)", + reactionBg: "#373737", + reactionChosen: "rgba(134, 158, 255, 0.3)", }; export default { lightTheme, darkTheme };