Emoji reactions (#176)
This commit is contained in:
parent
c170f81054
commit
fe3c189f00
|
@ -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 (
|
||||
<Icon
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 22 22"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
|
@ -40,9 +36,16 @@ export function ReactionSvg({ width, height, className }: ReactionProps) {
|
|||
}
|
||||
|
||||
const Icon = styled.svg`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
fill: ${({ theme }) => theme.secondary};
|
||||
|
||||
&:hover {
|
||||
fill: ${({ theme }) => theme.tertiary};
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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<React.SetStateAction<BaseEmoji[]>>;
|
||||
}
|
||||
|
||||
export function MessageReactions({
|
||||
messageReactions,
|
||||
setMessageReactions,
|
||||
}: MessageReactionsProps) {
|
||||
const isMyReactionIncluded = (emoji: BaseEmoji) =>
|
||||
messageReactions.includes(emoji); // temporary function while message reactions are not added to waku
|
||||
|
||||
return (
|
||||
<Reactions>
|
||||
{messageReactions.map((reaction) => (
|
||||
<EmojiReaction
|
||||
className={`${isMyReactionIncluded(reaction) && "chosen"}`}
|
||||
>
|
||||
<Emoji emoji={reaction} set={"twitter"} size={16} />
|
||||
<p>1</p>
|
||||
</EmojiReaction>
|
||||
))}
|
||||
<ReactionButton
|
||||
messageReactions={messageReactions}
|
||||
setMessageReactions={setMessageReactions}
|
||||
className="small"
|
||||
/>
|
||||
</Reactions>
|
||||
);
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
`;
|
|
@ -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<BaseEmoji[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mentioned)
|
||||
|
@ -146,14 +148,19 @@ export function UiMessage({
|
|||
setMentioned={setMentioned}
|
||||
/>
|
||||
</MessageText>
|
||||
{messageReactions.length > 0 && (
|
||||
<MessageReactions
|
||||
messageReactions={messageReactions}
|
||||
setMessageReactions={setMessageReactions}
|
||||
/>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
</UserMessageWrapper>
|
||||
{showReactions && <ReactionPicker />}
|
||||
<Reactions
|
||||
message={message}
|
||||
setReply={setReply}
|
||||
showReactions={showReactions}
|
||||
setShowReactions={setShowReactions}
|
||||
messageReactions={messageReactions}
|
||||
setMessageReactions={setMessageReactions}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
</MessageOuterWrapper>
|
||||
|
|
|
@ -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<React.SetStateAction<BaseEmoji[]>>;
|
||||
}
|
||||
|
||||
export function ReactionButton({
|
||||
className,
|
||||
messageReactions,
|
||||
setMessageReactions,
|
||||
}: ReactionButtonProps) {
|
||||
const ref = useRef(null);
|
||||
useClickOutside(ref, () => setShowReactions(false));
|
||||
|
||||
const [showReactions, setShowReactions] = useState(false);
|
||||
|
||||
return (
|
||||
<Wrapper ref={ref}>
|
||||
{showReactions && (
|
||||
<ReactionPicker
|
||||
messageReactions={messageReactions}
|
||||
setMessageReactions={setMessageReactions}
|
||||
className={className}
|
||||
/>
|
||||
)}
|
||||
<ReactionBtn
|
||||
onClick={() => setShowReactions(!showReactions)}
|
||||
className={className}
|
||||
>
|
||||
<ReactionSvg className={className} />
|
||||
{!className && !showReactions && <Tooltip tip="Add reaction" />}
|
||||
</ReactionBtn>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -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<React.SetStateAction<BaseEmoji[]>>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<Wrapper>
|
||||
<Wrapper className={className}>
|
||||
{emojiArr.map((emoji) => (
|
||||
<EmojiBtn key={emoji.id}>
|
||||
<EmojiBtn
|
||||
key={emoji.id}
|
||||
onClick={() => handleReaction(emoji)}
|
||||
className={`${messageReactions.includes(emoji) && "chosen"}`}
|
||||
>
|
||||
{" "}
|
||||
<Emoji
|
||||
emoji={emoji}
|
||||
|
@ -42,13 +65,19 @@ const Wrapper = styled.div`
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: -78px;
|
||||
right: -34px;
|
||||
top: -60px;
|
||||
background: ${({ theme }) => 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};
|
||||
}
|
||||
`;
|
|
@ -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<React.SetStateAction<BaseEmoji[]>>;
|
||||
}
|
||||
|
||||
export function Reactions({
|
||||
message,
|
||||
showReactions,
|
||||
setShowReactions,
|
||||
setReply,
|
||||
messageReactions,
|
||||
setMessageReactions,
|
||||
}: ReactionsProps) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<ReactionBtn onClick={() => setShowReactions(!showReactions)}>
|
||||
<ReactionSvg width={22} height={22} />
|
||||
<Tooltip tip="Add reaction" />
|
||||
</ReactionBtn>
|
||||
<ReactionButton
|
||||
messageReactions={messageReactions}
|
||||
setMessageReactions={setMessageReactions}
|
||||
/>
|
||||
<ReactionBtn
|
||||
onClick={() =>
|
||||
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;
|
||||
}
|
||||
`;
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in New Issue