[WEB-5614] chore: work item detail comment and sidebar enhancements (#8397)

This commit is contained in:
Anmol Singh Bhatia 2025-12-19 16:09:46 +05:30 committed by GitHub
parent f3d5e7def3
commit df69886080
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 72 additions and 29 deletions

View file

@ -1,7 +1,7 @@
import type { FC } from "react";
import { useRef, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
// plane imports
import { EmojiReactionButton, EmojiReactionPicker } from "@plane/propel/emoji-reaction";
import type { EditorRefApi } from "@plane/editor";
import type { TIssueComment, TCommentsOperations } from "@plane/types";
// plane web imports
@ -35,11 +35,23 @@ export const CommentCard = observer(function CommentCard(props: TCommentCard) {
} = props;
// states
const [isEditing, setIsEditing] = useState(false);
const [isPickerOpen, setIsPickerOpen] = useState(false);
// refs
const readOnlyEditorRef = useRef<EditorRefApi>(null);
// derived values
const workspaceId = comment?.workspace;
const userReactions = comment?.id ? activityOperations.userReactions(comment.id) : undefined;
const handleEmojiSelect = useCallback(
(emoji: string) => {
if (!userReactions || !comment?.id) return;
// emoji is already in decimal string format from EmojiReactionPicker
void activityOperations.react(comment.id, emoji, userReactions);
},
[activityOperations, comment?.id, userReactions]
);
if (!comment || !workspaceId) return null;
return (
@ -47,13 +59,24 @@ export const CommentCard = observer(function CommentCard(props: TCommentCard) {
comment={comment}
quickActions={
!disabled && (
<CommentQuickActions
activityOperations={activityOperations}
comment={comment}
setEditMode={() => setIsEditing(true)}
showAccessSpecifier={showAccessSpecifier}
showCopyLinkOption={showCopyLinkOption}
/>
<div className="flex items-center gap-1">
<EmojiReactionPicker
isOpen={isPickerOpen}
handleToggle={setIsPickerOpen}
onChange={handleEmojiSelect}
disabled={disabled}
label={<EmojiReactionButton onAddReaction={() => setIsPickerOpen(true)} />}
placement="bottom-start"
/>
<CommentQuickActions
activityOperations={activityOperations}
comment={comment}
setEditMode={() => setIsEditing(true)}
showAccessSpecifier={showAccessSpecifier}
showCopyLinkOption={showCopyLinkOption}
/>
</div>
)
}
ends={ends}

View file

@ -61,6 +61,12 @@ export const CommentReactions = observer(function CommentReactions(props: TProps
if (!userReactions) return null;
// Don't render anything if there are no reactions and it's disabled
if (reactions.length === 0 && disabled) return null;
// Don't show the add button if there are no reactions
const showAddButton = !disabled && reactions.length > 0;
return (
<div className="relative">
<EmojiReactionPicker
@ -72,7 +78,7 @@ export const CommentReactions = observer(function CommentReactions(props: TProps
<EmojiReactionGroup
reactions={reactions}
onReactionClick={handleReactionClick}
showAddButton={!disabled}
showAddButton={showAddButton}
onAddReaction={() => setIsPickerOpen(true)}
/>
}

View file

@ -1,9 +1,10 @@
import { useMemo } from "react";
import { observer } from "mobx-react";
import { Globe2, Link, Lock, Pencil, Trash2 } from "lucide-react";
import { Globe2, Link, Lock, Pencil, Trash2, MoreHorizontal } from "lucide-react";
// plane imports
import { EIssueCommentAccessSpecifier } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { IconButton } from "@plane/propel/icon-button";
import type { TIssueComment, TCommentsOperations } from "@plane/types";
import type { TContextMenuItem } from "@plane/ui";
import { CustomMenu } from "@plane/ui";
@ -76,7 +77,7 @@ export const CommentQuickActions = observer(function CommentQuickActions(props:
);
return (
<CustomMenu ellipsis closeOnSelect>
<CustomMenu customButton={<IconButton icon={MoreHorizontal} variant="ghost" size="sm" />} closeOnSelect>
{MENU_ITEMS.map((item) => {
if (item.shouldRender === false) return null;

View file

@ -13,7 +13,7 @@ export function SidebarPropertyListItem(props: TSidebarPropertyListItemProps) {
const { icon: Icon, label, children, appendElement, childrenClassName } = props;
return (
<div className="flex items-center gap-2">
<div className="flex items-start gap-2">
<div className="flex shrink-0 items-center gap-1.5 w-30 text-body-xs-regular text-tertiary h-7.5">
<Icon className="size-4 shrink-0" />
<span>{label}</span>

View file

@ -4,6 +4,7 @@ import { stringToEmoji } from "../emoji-icon-picker";
import { AddReactionIcon } from "../icons";
import { Tooltip } from "../tooltip";
import { cn } from "../utils";
import { IconButton } from "../icon-button";
export interface EmojiReactionType {
emoji: string;
@ -100,22 +101,17 @@ const EmojiReactionButton = React.forwardRef(function EmojiReactionButton(
ref: React.ForwardedRef<HTMLButtonElement>
) {
return (
<button
ref={ref}
onClick={onAddReaction}
className={cn(
"inline-flex items-center justify-center rounded-full border border-dashed border-strong",
"bg-surface-1 text-placeholder transition-all duration-200",
"hover:border-accent-strong hover:text-accent-primary hover:bg-accent-primary/5",
"focus:outline-none focus:ring-2 focus:ring-accent-strong/20 focus:ring-offset-1",
"h-6 w-6",
className
)}
title="Add reaction"
{...props}
>
<AddReactionIcon className="h-3 w-3" />
</button>
<Tooltip tooltipContent="Add reaction">
<IconButton
ref={ref}
icon={AddReactionIcon}
variant="ghost"
size="sm"
onClick={onAddReaction}
className={className}
{...props}
/>
</Tooltip>
);
});

View file

@ -2,3 +2,4 @@ export * from "./chevron-down";
export * from "./chevron-left";
export * from "./chevron-right";
export * from "./chevron-up";
export * from "./reply-icon";

View file

@ -0,0 +1,13 @@
import { IconWrapper } from "../icon-wrapper";
import type { ISvgIcons } from "../type";
export function ReplyIcon({ color = "currentColor", ...rest }: ISvgIcons) {
return (
<IconWrapper color={color} clipPathId="clip0_2890_23" {...rest}>
<path
d="M5.52865 4.19526C5.789 3.93491 6.211 3.93491 6.47135 4.19526C6.73168 4.45561 6.7317 4.87763 6.47135 5.13797L4.27604 7.33328H10.6667C11.5507 7.33328 12.3983 7.68472 13.0234 8.30985C13.6485 8.93496 14 9.78257 14 10.6666V11.9999C14 12.3681 13.7015 12.6666 13.3333 12.6666C12.9652 12.6666 12.6667 12.3681 12.6667 11.9999V10.6666C12.6667 10.1362 12.4558 9.62762 12.0807 9.25255C11.7526 8.92442 11.3223 8.72189 10.8646 8.67638L10.6667 8.66662H4.27604L6.47135 10.8619C6.73168 11.1223 6.7317 11.5443 6.47135 11.8046C6.21101 12.065 5.78899 12.065 5.52865 11.8046L2.19531 8.4713C2.1642 8.4402 2.13648 8.40583 2.11198 8.36909C2.09464 8.3431 2.07971 8.31603 2.06641 8.28836C2.05178 8.25798 2.03859 8.22669 2.02865 8.19396C2.02526 8.18281 2.02297 8.17139 2.02018 8.16011C2.0075 8.10875 2 8.05523 2 7.99995C2 7.94683 2.00649 7.89518 2.01823 7.84565C2.02148 7.83195 2.02452 7.81815 2.02865 7.80464C2.03858 7.77214 2.05185 7.74107 2.06641 7.71089C2.07918 7.68436 2.09353 7.65841 2.11003 7.63341C2.13495 7.59564 2.16344 7.56047 2.19531 7.5286L5.52865 4.19526Z"
fill={color}
/>
</IconWrapper>
);
}

View file

@ -16,6 +16,7 @@ export const ArrowsIconsMap = [
{ icon: <Icon name="arrow.chevron-left" />, title: "ChevronLeftIcon" },
{ icon: <Icon name="arrow.chevron-right" />, title: "ChevronRightIcon" },
{ icon: <Icon name="arrow.chevron-up" />, title: "ChevronUpIcon" },
{ icon: <Icon name="arrow.reply" />, title: "ReplyIcon" },
];
export const WorkspaceIconsMap = [

View file

@ -12,6 +12,7 @@ import { ChevronDownIcon } from "./arrows/chevron-down";
import { ChevronLeftIcon } from "./arrows/chevron-left";
import { ChevronRightIcon } from "./arrows/chevron-right";
import { ChevronUpIcon } from "./arrows/chevron-up";
import { ReplyIcon } from "./arrows/reply-icon";
import { DefaultIcon } from "./default-icon";
import { BoardLayoutIcon } from "./layouts/board-icon";
import { CalendarLayoutIcon } from "./layouts/calendar-icon";
@ -139,6 +140,7 @@ export const ICON_REGISTRY = {
"arrow.chevron-left": ChevronLeftIcon,
"arrow.chevron-right": ChevronRightIcon,
"arrow.chevron-up": ChevronUpIcon,
"arrow.reply": ReplyIcon,
// Default fallback
default: DefaultIcon,