[WEB-720] chore: reaction tooltip added (#3945)

* chore: reaction tooltip added

* chore: reaction tooltip updated

* chore: issue reaction tooltip updated

* chore: helper function updated
This commit is contained in:
Anmol Singh Bhatia 2024-03-12 19:12:25 +05:30 committed by GitHub
parent c97b994311
commit 8aca74c68d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 85 additions and 35 deletions

View File

@ -1,10 +1,11 @@
import { FC, useMemo } from "react"; import { FC, useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { TOAST_TYPE, setToast } from "@plane/ui"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { useIssueDetail } from "hooks/store"; import { useIssueDetail, useMember } from "hooks/store";
// ui // helper
import { formatTextList } from "helpers/issue.helper";
// types // types
import { IUser } from "@plane/types"; import { IUser } from "@plane/types";
import { ReactionSelector } from "./reaction-selector"; import { ReactionSelector } from "./reaction-selector";
@ -21,10 +22,11 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
// hooks // hooks
const { const {
commentReaction: { getCommentReactionsByCommentId, commentReactionsByUser }, commentReaction: { getCommentReactionsByCommentId, commentReactionsByUser, getCommentReactionById },
createCommentReaction, createCommentReaction,
removeCommentReaction, removeCommentReaction,
} = useIssueDetail(); } = useIssueDetail();
const { getUserDetails } = useMember();
const reactionIds = getCommentReactionsByCommentId(commentId); const reactionIds = getCommentReactionsByCommentId(commentId);
const userReactions = commentReactionsByUser(commentId, currentUser.id).map((r) => r.reaction); const userReactions = commentReactionsByUser(commentId, currentUser.id).map((r) => r.reaction);
@ -73,6 +75,17 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
[workspaceSlug, projectId, commentId, currentUser, createCommentReaction, removeCommentReaction, userReactions] [workspaceSlug, projectId, commentId, currentUser, createCommentReaction, removeCommentReaction, userReactions]
); );
const getReactionUsers = (reaction: string): string => {
const reactionUsers = (reactionIds?.[reaction] || [])
.map((reactionId) => {
const reactionDetails = getCommentReactionById(reactionId);
return reactionDetails ? getUserDetails(reactionDetails.actor)?.display_name : null;
})
.filter((displayName): displayName is string => !!displayName);
const formattedUsers = formatTextList(reactionUsers);
return formattedUsers;
};
return ( return (
<div className="mt-4 relative flex items-center gap-1.5"> <div className="mt-4 relative flex items-center gap-1.5">
<ReactionSelector <ReactionSelector
@ -87,19 +100,21 @@ export const IssueCommentReaction: FC<TIssueCommentReaction> = observer((props)
(reaction) => (reaction) =>
reactionIds[reaction]?.length > 0 && ( reactionIds[reaction]?.length > 0 && (
<> <>
<button <Tooltip tooltipContent={getReactionUsers(reaction)}>
type="button" <button
onClick={() => issueCommentReactionOperations.react(reaction)} type="button"
key={reaction} onClick={() => issueCommentReactionOperations.react(reaction)}
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${ key={reaction}
userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80" className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
}`} userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80"
> }`}
<span>{renderEmoji(reaction)}</span> >
<span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}> <span>{renderEmoji(reaction)}</span>
{(reactionIds || {})[reaction].length}{" "} <span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}>
</span> {(reactionIds || {})[reaction].length}{" "}
</button> </span>
</button>
</Tooltip>
</> </>
) )
)} )}

View File

@ -1,10 +1,12 @@
import { FC, useMemo } from "react"; import { FC, useMemo } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // hooks
import { TOAST_TYPE, setToast } from "@plane/ui"; import { useIssueDetail, useMember } from "hooks/store";
import { renderEmoji } from "helpers/emoji.helper";
import { useIssueDetail } from "hooks/store";
// ui // ui
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// helpers
import { renderEmoji } from "helpers/emoji.helper";
import { formatTextList } from "helpers/issue.helper";
// types // types
import { IUser } from "@plane/types"; import { IUser } from "@plane/types";
import { ReactionSelector } from "./reaction-selector"; import { ReactionSelector } from "./reaction-selector";
@ -20,10 +22,11 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
const { workspaceSlug, projectId, issueId, currentUser } = props; const { workspaceSlug, projectId, issueId, currentUser } = props;
// hooks // hooks
const { const {
reaction: { getReactionsByIssueId, reactionsByUser }, reaction: { getReactionsByIssueId, reactionsByUser, getReactionById },
createReaction, createReaction,
removeReaction, removeReaction,
} = useIssueDetail(); } = useIssueDetail();
const { getUserDetails } = useMember();
const reactionIds = getReactionsByIssueId(issueId); const reactionIds = getReactionsByIssueId(issueId);
const userReactions = reactionsByUser(issueId, currentUser.id).map((r) => r.reaction); const userReactions = reactionsByUser(issueId, currentUser.id).map((r) => r.reaction);
@ -72,6 +75,18 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
[workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions] [workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, userReactions]
); );
const getReactionUsers = (reaction: string): string => {
const reactionUsers = (reactionIds?.[reaction] || [])
.map((reactionId) => {
const reactionDetails = getReactionById(reactionId);
return reactionDetails ? getUserDetails(reactionDetails.actor_id)?.display_name : null;
})
.filter((displayName): displayName is string => !!displayName);
const formattedUsers = formatTextList(reactionUsers);
return formattedUsers;
};
return ( return (
<div className="mt-4 relative flex items-center gap-1.5"> <div className="mt-4 relative flex items-center gap-1.5">
<ReactionSelector size="md" position="top" value={userReactions} onSelect={issueReactionOperations.react} /> <ReactionSelector size="md" position="top" value={userReactions} onSelect={issueReactionOperations.react} />
@ -81,19 +96,21 @@ export const IssueReaction: FC<TIssueReaction> = observer((props) => {
(reaction) => (reaction) =>
reactionIds[reaction]?.length > 0 && ( reactionIds[reaction]?.length > 0 && (
<> <>
<button <Tooltip tooltipContent={getReactionUsers(reaction)}>
type="button" <button
onClick={() => issueReactionOperations.react(reaction)} type="button"
key={reaction} onClick={() => issueReactionOperations.react(reaction)}
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${ key={reaction}
userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80" className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
}`} userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80"
> }`}
<span>{renderEmoji(reaction)}</span> >
<span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}> <span>{renderEmoji(reaction)}</span>
{(reactionIds || {})[reaction].length}{" "} <span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}>
</span> {(reactionIds || {})[reaction].length}{" "}
</button> </span>
</button>
</Tooltip>
</> </>
) )
)} )}

View File

@ -184,3 +184,21 @@ export function getChangedIssuefields(formData: Partial<TIssue>, dirtyFields: {
return changedFields; return changedFields;
} }
export const formatTextList = (TextArray: string[]): string => {
const count = TextArray.length;
switch (count) {
case 0:
return "";
case 1:
return TextArray[0];
case 2:
return `${TextArray[0]} and ${TextArray[1]}`;
case 3:
return `${TextArray.slice(0, 2).join(", ")}, and ${TextArray[2]}`;
case 4:
return `${TextArray.slice(0, 3).join(", ")}, and ${TextArray[3]}`;
default:
return `${TextArray.slice(0, 3).join(", ")}, and +${count - 3} more`;
}
};