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";
|
import styled from "styled-components";
|
||||||
|
|
||||||
type ReactionProps = {
|
type ReactionProps = {
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ReactionSvg({ width, height, className }: ReactionProps) {
|
export function ReactionSvg({ className }: ReactionProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
viewBox="0 0 22 22"
|
viewBox="0 0 22 22"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className={className}
|
className={className}
|
||||||
|
@ -40,9 +36,16 @@ export function ReactionSvg({ width, height, className }: ReactionProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = styled.svg`
|
const Icon = styled.svg`
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
fill: ${({ theme }) => theme.secondary};
|
fill: ${({ theme }) => theme.secondary};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
fill: ${({ theme }) => theme.tertiary};
|
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 React, { useEffect, useMemo, useState } from "react";
|
||||||
import { utils } from "status-communities/dist/cjs";
|
import { utils } from "status-communities/dist/cjs";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
@ -11,13 +12,13 @@ import { ChatMessage } from "../../models/ChatMessage";
|
||||||
import { equalDate } from "../../utils";
|
import { equalDate } from "../../utils";
|
||||||
import { ChatMessageContent } from "../Chat/ChatMessageContent";
|
import { ChatMessageContent } from "../Chat/ChatMessageContent";
|
||||||
import { ContactMenu } from "../Form/ContactMenu";
|
import { ContactMenu } from "../Form/ContactMenu";
|
||||||
import { ReactionPicker } from "../Form/ReactionPicker";
|
|
||||||
import { Reactions } from "../Form/Reactions";
|
|
||||||
import { Icon } from "../Icons/Icon";
|
import { Icon } from "../Icons/Icon";
|
||||||
import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
|
import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
|
||||||
import { UserLogo } from "../Members/UserLogo";
|
import { UserLogo } from "../Members/UserLogo";
|
||||||
|
import { Reactions } from "../Reactions/Reactions";
|
||||||
|
|
||||||
import { MessageQuote } from "./MessageQuote";
|
import { MessageQuote } from "./MessageQuote";
|
||||||
|
import { MessageReactions } from "./MessageReactions";
|
||||||
import {
|
import {
|
||||||
ContentWrapper,
|
ContentWrapper,
|
||||||
DateSeparator,
|
DateSeparator,
|
||||||
|
@ -62,8 +63,9 @@ export function UiMessage({
|
||||||
[message.sender, contacts]
|
[message.sender, contacts]
|
||||||
);
|
);
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const [showReactions, setShowReactions] = useState(false);
|
|
||||||
const [mentioned, setMentioned] = useState(false);
|
const [mentioned, setMentioned] = useState(false);
|
||||||
|
const [messageReactions, setMessageReactions] = useState<BaseEmoji[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mentioned)
|
if (mentioned)
|
||||||
|
@ -146,14 +148,19 @@ export function UiMessage({
|
||||||
setMentioned={setMentioned}
|
setMentioned={setMentioned}
|
||||||
/>
|
/>
|
||||||
</MessageText>
|
</MessageText>
|
||||||
|
{messageReactions.length > 0 && (
|
||||||
|
<MessageReactions
|
||||||
|
messageReactions={messageReactions}
|
||||||
|
setMessageReactions={setMessageReactions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
</UserMessageWrapper>
|
</UserMessageWrapper>
|
||||||
{showReactions && <ReactionPicker />}
|
|
||||||
<Reactions
|
<Reactions
|
||||||
message={message}
|
message={message}
|
||||||
setReply={setReply}
|
setReply={setReply}
|
||||||
showReactions={showReactions}
|
messageReactions={messageReactions}
|
||||||
setShowReactions={setShowReactions}
|
setMessageReactions={setMessageReactions}
|
||||||
/>
|
/>
|
||||||
</MessageWrapper>
|
</MessageWrapper>
|
||||||
</MessageOuterWrapper>
|
</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 data from "emoji-mart/data/all.json";
|
||||||
import React from "react";
|
import React, { useCallback } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const emojiHeart = getEmojiDataFromNative("❤️", "twitter", data);
|
const emojiHeart = getEmojiDataFromNative("❤️", "twitter", data);
|
||||||
|
@ -19,11 +19,34 @@ const emojiArr = [
|
||||||
emojiRage,
|
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 (
|
return (
|
||||||
<Wrapper>
|
<Wrapper className={className}>
|
||||||
{emojiArr.map((emoji) => (
|
{emojiArr.map((emoji) => (
|
||||||
<EmojiBtn key={emoji.id}>
|
<EmojiBtn
|
||||||
|
key={emoji.id}
|
||||||
|
onClick={() => handleReaction(emoji)}
|
||||||
|
className={`${messageReactions.includes(emoji) && "chosen"}`}
|
||||||
|
>
|
||||||
{" "}
|
{" "}
|
||||||
<Emoji
|
<Emoji
|
||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
|
@ -42,13 +65,19 @@ const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: -34px;
|
||||||
top: -78px;
|
top: -60px;
|
||||||
background: ${({ theme }) => theme.bodyBackgroundColor};
|
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
border-radius: 16px 16px 4px 16px;
|
border-radius: 16px 16px 4px 16px;
|
||||||
padding: 8px 12px;
|
padding: 8px;
|
||||||
visibility: hidden;
|
|
||||||
|
&.small {
|
||||||
|
right: unset;
|
||||||
|
left: -100px;
|
||||||
|
transform: none;
|
||||||
|
border-radius: 16px 16px 16px 4px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmojiBtn = styled.button`
|
const EmojiBtn = styled.button`
|
||||||
|
@ -62,4 +91,9 @@ const EmojiBtn = styled.button`
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${({ theme }) => theme.inputColor};
|
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 React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { Reply } from "../../hooks/useReply";
|
import { Reply } from "../../hooks/useReply";
|
||||||
import { ChatMessage } from "../../models/ChatMessage";
|
import { ChatMessage } from "../../models/ChatMessage";
|
||||||
import { ReactionSvg } from "../Icons/ReactionIcon";
|
import { Tooltip } from "../Form/Tooltip";
|
||||||
import { ReplySvg } from "../Icons/ReplyIcon";
|
import { ReplySvg } from "../Icons/ReplyIcon";
|
||||||
|
|
||||||
import { Tooltip } from "./Tooltip";
|
import { ReactionBtn, ReactionButton } from "./ReactionButton";
|
||||||
|
|
||||||
interface ReactionsProps {
|
interface ReactionsProps {
|
||||||
message: ChatMessage;
|
message: ChatMessage;
|
||||||
showReactions: boolean;
|
|
||||||
setShowReactions: (val: boolean) => void;
|
|
||||||
setReply: (val: Reply | undefined) => void;
|
setReply: (val: Reply | undefined) => void;
|
||||||
|
messageReactions: BaseEmoji[];
|
||||||
|
setMessageReactions: React.Dispatch<React.SetStateAction<BaseEmoji[]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Reactions({
|
export function Reactions({
|
||||||
message,
|
message,
|
||||||
showReactions,
|
|
||||||
setShowReactions,
|
|
||||||
setReply,
|
setReply,
|
||||||
|
messageReactions,
|
||||||
|
setMessageReactions,
|
||||||
}: ReactionsProps) {
|
}: ReactionsProps) {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<ReactionBtn onClick={() => setShowReactions(!showReactions)}>
|
<ReactionButton
|
||||||
<ReactionSvg width={22} height={22} />
|
messageReactions={messageReactions}
|
||||||
<Tooltip tip="Add reaction" />
|
setMessageReactions={setMessageReactions}
|
||||||
</ReactionBtn>
|
/>
|
||||||
<ReactionBtn
|
<ReactionBtn
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setReply({
|
setReply({
|
||||||
|
@ -55,26 +56,3 @@ const Wrapper = styled.div`
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
visibility: hidden;
|
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;
|
mentionBg: string;
|
||||||
mentionBgHover: string;
|
mentionBgHover: string;
|
||||||
shadow: string;
|
shadow: string;
|
||||||
|
reactionBg: string;
|
||||||
|
reactionChosen: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lightTheme: Theme = {
|
export const lightTheme: Theme = {
|
||||||
|
@ -60,6 +62,8 @@ export const lightTheme: Theme = {
|
||||||
mentionBgHover: "#D4F3FA",
|
mentionBgHover: "#D4F3FA",
|
||||||
shadow:
|
shadow:
|
||||||
"0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)",
|
"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 = {
|
export const darkTheme: Theme = {
|
||||||
|
@ -93,6 +97,8 @@ export const darkTheme: Theme = {
|
||||||
mentionBgHover: "#002D39",
|
mentionBgHover: "#002D39",
|
||||||
shadow:
|
shadow:
|
||||||
"0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)",
|
"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 };
|
export default { lightTheme, darkTheme };
|
||||||
|
|
Loading…
Reference in New Issue