[WEB-5515]fix: comment replies #4930

This commit is contained in:
Vamsi Krishna
2025-12-02 18:08:20 +05:30
committed by GitHub
parent eda7f7decf
commit 54ee0dd64e
23 changed files with 214 additions and 126 deletions
@@ -41,7 +41,7 @@ export const CommentBlock = observer(function CommentBlock(props: TCommentBlock)
ref={commentBlockRef}
>
<div
className="absolute left-[13px] top-0 bottom-0 w-0.5 transition-border duration-1000 bg-custom-background-80"
className="absolute left-[13px] top-0 bottom-0 w-px transition-border duration-1000 bg-custom-background-80"
aria-hidden
/>
<div
@@ -1,3 +1,4 @@
import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
@@ -26,6 +27,7 @@ export type TCommentCardDisplayProps = {
workspaceSlug: string;
isEditing?: boolean;
setIsEditing?: (isEditing: boolean) => void;
renderFooter?: (ReactionsComponent: ReactNode) => ReactNode;
};
export const CommentCardDisplay = observer(function CommentCardDisplay(props: TCommentCardDisplayProps) {
@@ -40,6 +42,7 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: TC
workspaceSlug,
isEditing = false,
setIsEditing,
renderFooter,
} = props;
// states
const [highlightClassName, setHighlightClassName] = useState("");
@@ -99,11 +102,17 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: TC
displayConfig={{
fontSize: "small-font",
}}
parentClassName="border-none"
parentClassName="border-none pl-1"
/>
<div className="pl-2">
<CommentReactions comment={comment} disabled={disabled} activityOperations={activityOperations} />
</div>
{renderFooter ? (
renderFooter(
<CommentReactions comment={comment} disabled={disabled} activityOperations={activityOperations} />
)
) : (
<div className="pl-2">
<CommentReactions comment={comment} disabled={disabled} activityOperations={activityOperations} />
</div>
)}
</>
)}
</div>
@@ -6,7 +6,7 @@ import type { EditorRefApi } from "@plane/editor";
import { CloseIcon } from "@plane/propel/icons";
// plane imports
import type { TCommentsOperations, TIssueComment } from "@plane/types";
import { isCommentEmpty } from "@plane/utils";
import { cn, isCommentEmpty } from "@plane/utils";
// components
import { LiteTextEditor } from "@/components/editor/lite-text";
@@ -99,7 +99,7 @@ export const CommentCardEditForm = observer(function CommentCardEditForm(props:
return asset_id;
}}
projectId={projectId}
parentClassName="p-2"
parentClassName="p-2 bg-custom-background-100"
displayConfig={{
fontSize: "small-font",
}}
@@ -111,24 +111,41 @@ export const CommentCardEditForm = observer(function CommentCardEditForm(props:
type="button"
onClick={handleSubmit(onEnter)}
disabled={isDisabled}
className={`group rounded border border-green-500 bg-green-500/20 p-2 shadow-md duration-300 ${
isEmpty ? "cursor-not-allowed bg-gray-200" : "hover:bg-green-500"
}`}
className={cn(
"group rounded border p-2 shadow-md duration-300",
isDisabled
? "cursor-not-allowed border-green-500/50 bg-green-500/10"
: "border-green-500 bg-green-500/20 hover:bg-green-500"
)}
>
<Check
className={`h-3 w-3 text-green-500 duration-300 ${isEmpty ? "text-black" : "group-hover:text-white"}`}
className={cn(
"h-3 w-3 duration-300",
isDisabled ? "text-green-500/50" : "text-green-500 group-hover:text-white"
)}
/>
</button>
)}
<button
type="button"
className="group rounded border border-red-500 bg-red-500/20 p-2 shadow-md duration-300 hover:bg-red-500"
disabled={isSubmitting}
className={cn(
"group rounded border p-2 shadow-md duration-300",
isSubmitting
? "cursor-not-allowed border-red-500/50 bg-red-500/10"
: "border-red-500 bg-red-500/20 hover:bg-red-500"
)}
onClick={() => {
setIsEditing(false);
editorRef.current?.setEditorValue(comment.comment_html ?? "<p></p>");
}}
>
<CloseIcon className="size-3 text-red-500 duration-300 group-hover:text-white" />
<CloseIcon
className={cn(
"size-3 duration-300",
isSubmitting ? "text-red-500/50" : "text-red-500 group-hover:text-white"
)}
/>
</button>
</div>
</form>
@@ -57,15 +57,13 @@ export const CommentCard = observer(function CommentCard(props: TCommentCard) {
workspaceSlug={workspaceSlug}
isEditing={isEditing}
setIsEditing={setIsEditing}
renderQuickActions={(handleReply) => (
renderQuickActions={() => (
<CommentQuickActions
activityOperations={activityOperations}
comment={comment}
setEditMode={() => setIsEditing(true)}
showAccessSpecifier={showAccessSpecifier}
showCopyLinkOption={showCopyLinkOption}
showReplyOption={enableReplies}
handleReply={handleReply}
/>
)}
enableReplies={enableReplies}
@@ -16,20 +16,10 @@ type TCommentCard = {
setEditMode: () => void;
showAccessSpecifier: boolean;
showCopyLinkOption: boolean;
showReplyOption: boolean;
handleReply?: () => void;
};
export const CommentQuickActions: FC<TCommentCard> = observer((props) => {
const {
activityOperations,
comment,
setEditMode,
showAccessSpecifier,
showCopyLinkOption,
showReplyOption,
handleReply,
} = props;
const { activityOperations, comment, setEditMode, showAccessSpecifier, showCopyLinkOption } = props;
// store hooks
const { data: currentUser } = useUser();
// derived values
@@ -54,10 +44,10 @@ export const CommentQuickActions: FC<TCommentCard> = observer((props) => {
: EIssueCommentAccessSpecifier.INTERNAL,
}),
handleDelete: () => activityOperations.removeComment(comment.id),
handleReply,
showReplyOption,
});
if(MENU_ITEMS.length === 0) return null;
return (
<CustomMenu ellipsis closeOnSelect>
{MENU_ITEMS.map((item) => {
@@ -239,12 +239,10 @@ interface UseCommentMenuItemsProps {
isAuthor: boolean;
showAccessSpecifier: boolean;
showCopyLinkOption: boolean;
showReplyOption: boolean;
handleEdit: () => void;
handleCopyLink: () => void;
handleToggleAccess: () => void;
handleDelete: () => void;
handleReply?: () => void;
}
export const useCommentMenuItems = (props: UseCommentMenuItemsProps): TContextMenuItem[] => {
@@ -254,27 +252,18 @@ export const useCommentMenuItems = (props: UseCommentMenuItemsProps): TContextMe
isAuthor,
showAccessSpecifier,
showCopyLinkOption,
showReplyOption,
handleEdit,
handleCopyLink,
handleToggleAccess,
handleDelete,
handleReply,
} = props;
const replyFeature = factory.useCommentReplyFeature?.({
commentId: comment.id,
handleReply,
shouldRender: showReplyOption,
});
// Check if access is INTERNAL (0 or "INTERNAL")
const isInternal = comment.access === 0 || comment.access === "INTERNAL" || comment.access === "0";
// Assemble final menu items - order defined here
const items = [
factory.createCommentEditMenuItem(handleEdit, isAuthor),
...(replyFeature?.items ?? []),
factory.createCommentCopyLinkMenuItem(handleCopyLink, showCopyLinkOption),
factory.createCommentAccessSpecifierMenuItem(handleToggleAccess, isInternal, showAccessSpecifier),
factory.createCommentDeleteMenuItem(handleDelete, isAuthor),
@@ -56,7 +56,7 @@ export const IssueActivityBlockComponent = observer(function IssueActivityBlockC
}`}
ref={activityBlockRef}
>
<div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
<div className="absolute left-[13px] top-0 bottom-0 w-px bg-custom-background-80" aria-hidden />
<div
className={cn(
"flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden flex justify-center items-center z-[4] bg-custom-background-80 transition-border duration-1000 border-2 border-transparent",
@@ -3,9 +3,12 @@ import { useCallback, useRef } from "react";
import { observer } from "mobx-react";
// plane imports
import type { EditorRefApi } from "@plane/editor";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { Separator } from "@plane/propel/separator";
import { Tooltip } from "@plane/propel/tooltip";
import { Avatar } from "@plane/ui";
import { calculateTimeAgo, getFileURL, renderFormattedDate, renderFormattedTime } from "@plane/utils";
import { calculateTimeAgo, cn, getFileURL, renderFormattedDate, renderFormattedTime } from "@plane/utils";
// components
import { CommentCardDisplay as BaseCommentCardDisplay } from "@/components/comments/card/display";
import type { TCommentCardDisplayProps } from "@/components/comments/card/display";
@@ -16,7 +19,7 @@ import type { CommentRepliesRootHandle } from "../replies/root";
import { CommentRepliesRoot } from "../replies/root";
type Props = TCommentCardDisplayProps & {
renderQuickActions: (handleReply: () => void) => ReactNode;
renderQuickActions: () => ReactNode;
enableReplies: boolean;
isReply?: boolean;
};
@@ -38,6 +41,8 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: Pr
// refs
const repliesRootRef = useRef<CommentRepliesRootHandle>(null);
const editorRef = useRef<EditorRefApi>(null);
// hooks
const { t } = useTranslation();
// store hooks
const { getUserDetails } = useMember();
// derived values
@@ -48,27 +53,27 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: Pr
const avatarUrl = userDetails?.avatar_url ?? comment?.actor_detail?.avatar_url;
const handleReply = useCallback(() => {
editorRef.current?.setFocusAtPosition(0);
repliesRootRef.current?.showReplyEditor();
}, []);
const areRepliesAvailable = comment.reply_count !== undefined && comment.reply_count > 0;
const shouldShowIndicator = isReply || areRepliesAvailable;
const shouldShowReplyButton = enableReplies && !isReply && !disabled;
return (
<>
<div className="relative">
<div className={cn("relative", isReply && "pt-2")}>
{shouldShowIndicator && (
<div
className="absolute left-[7px] top-1 -bottom-1 w-px transition-border duration-1000 bg-custom-background-80"
aria-hidden
/>
)}
<div className="flex relative w-full gap-2 items-center z-10">
<div className="flex relative w-full gap-2 items-center">
<Avatar size="sm" name={displayName} src={getFileURL(avatarUrl)} className="shrink-0" />
<div className="flex-1 flex flex-wrap items-center gap-1">
<div className="text-xs font-medium">{displayName}</div>
<div className="text-xs text-custom-text-300">
commented{" "}
{isReply ? "replied " : "commented "}
<Tooltip
tooltipContent={`${renderFormattedDate(comment.created_at)} at ${renderFormattedTime(comment.created_at)}`}
position="bottom"
@@ -80,10 +85,10 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: Pr
</Tooltip>
</div>
</div>
{!disabled && <div className="shrink-0">{renderQuickActions(handleReply)}</div>}
{!disabled && <div className="shrink-0">{renderQuickActions()}</div>}
</div>
{/* Core: Comment content */}
<div className="ml-4 pb-4">
<div className="ml-4">
<BaseCommentCardDisplay
{...restProps}
entityId={entityId}
@@ -92,6 +97,24 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: Pr
workspaceSlug={workspaceSlug}
activityOperations={activityOperations}
showAccessSpecifier={showAccessSpecifier}
renderFooter={(ReactionsComponent) => (
<div className="flex items-center gap-1">
{shouldShowReplyButton && (
<>
<Button
variant="link-neutral"
size="sm"
onClick={handleReply}
className="hover:bg-custom-background-80 px-2.5"
>
{t("common.actions.reply")}
</Button>
<Separator orientation="vertical" className="h-4" />
</>
)}
<div className="px-2">{ReactionsComponent}</div>
</div>
)}
/>
</div>
</div>
@@ -102,12 +125,12 @@ export const CommentCardDisplay = observer(function CommentCardDisplay(props: Pr
workspaceSlug={workspaceSlug}
projectId={projectId}
entityId={entityId}
activityOperations={activityOperations}
commentId={comment.id}
repliesCount={comment.reply_count || 0}
repliedUserIds={comment.replied_user_ids || []}
lastReplyAt={comment.last_reply_at || null}
showAccessSpecifier={showAccessSpecifier}
activityOperations={activityOperations}
/>
)}
</>
@@ -41,7 +41,7 @@ export const CommentBlock: FC<TCommentBlock> = observer((props) => {
ref={commentBlockRef}
>
<div
className="absolute left-[13px] top-0 bottom-0 w-0.5 transition-border duration-1000 bg-custom-background-80"
className="absolute left-[13px] top-0 bottom-0 w-px transition-border duration-1000 bg-custom-background-80"
aria-hidden
/>
<div
@@ -58,6 +58,24 @@ export const useCommentRepliesOperations = (
return undefined;
}
},
updateReply: async (replyId, data) => {
try {
if (!workspaceSlug || !projectId || !issueId || !replyId) throw new Error("Missing fields");
await repliesStore.updateReply(workspaceSlug, projectId, issueId, replyId, data);
setToast({
title: t("common.success"),
type: TOAST_TYPE.SUCCESS,
message: t("issue.comments.replies.toast.update.success.message"),
});
} catch (error) {
console.error("Error updating reply:", error);
setToast({
title: t("common.error.label"),
type: TOAST_TYPE.ERROR,
message: t("issue.comments.replies.toast.update.error.message"),
});
}
},
deleteReply: async (replyId) => {
try {
if (!workspaceSlug || !projectId || !issueId || !replyId) throw new Error("Missing fields");
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { observer } from "mobx-react";
// plane imports
import type { EditorRefApi } from "@plane/editor";
@@ -30,11 +30,25 @@ export const ReplyCard = observer(function ReplyCard(props: Props) {
// derived values
const reply = getReply();
// Create wrapper operations that route updateComment to updateReply for replies
const replyActivityOperations: TCommentsOperations = useMemo(
() => ({
...activityOperations,
updateComment: async (commentId: string, data: Partial<TIssueComment>) => {
// Route to reply update operation
if (activityOperations.replyOperations?.updateReply) {
await activityOperations.replyOperations.updateReply(commentId, data);
}
},
}),
[activityOperations]
);
if (!reply) return null;
return (
<CommentCardDisplay
activityOperations={activityOperations}
activityOperations={replyActivityOperations}
entityId={entityId}
comment={reply}
disabled={false}
@@ -112,6 +112,7 @@ export const ReplyCreate = observer(function ReplyCreate(props: Props) {
}
}}
ref={editorRef}
value={null}
initialValue={value ?? "<p></p>"}
containerClassName="min-h-min"
onChange={(comment_json, comment_html) => onChange(comment_html)}
@@ -130,7 +131,7 @@ export const ReplyCreate = observer(function ReplyCreate(props: Props) {
return asset_id;
}}
showToolbarInitially={false}
parentClassName="p-2"
parentClassName="p-2 bg-custom-background-100"
placeholder={t("issue.comments.replies.create.placeholder")}
displayConfig={{
fontSize: "small-font",
@@ -1,4 +1,4 @@
import { useEffect, useState, useImperativeHandle, forwardRef, useRef } from "react";
import { useEffect, useState, useImperativeHandle, forwardRef } from "react";
import { uniq } from "lodash-es";
import { observer } from "mobx-react";
// plane imports
@@ -46,29 +46,17 @@ export const CommentRepliesRoot = observer(
} = props;
// states
const [isExpanded, setIsExpanded] = useState(false);
// refs
const previousRepliesCountRef = useRef(repliesCount);
// store hooks
const { getUserDetails } = useMember();
// Expose method to show editor - expands and shows editor
useImperativeHandle(ref, () => ({
showReplyEditor: () => {
setIsExpanded(true);
if (!isExpanded) {
setIsExpanded(true);
}
editorRef.current?.focus("end");
},
}));
// Auto-expand only when a new reply is added (repliesCount increases)
useEffect(() => {
const previousCount = previousRepliesCountRef.current;
const currentCount = repliesCount;
// Only auto-expand if replies count increased (new reply added) and not already expanded
if (currentCount > previousCount && currentCount > 0 && !isExpanded) {
setIsExpanded(true);
}
// Update ref for next render
previousRepliesCountRef.current = currentCount;
}, [repliesCount, isExpanded]);
// Fetch replies when expanded and has replies
useEffect(() => {
if (isExpanded && repliesCount > 0) {
@@ -79,53 +67,63 @@ export const CommentRepliesRoot = observer(
}, [isExpanded, repliesCount, commentId, activityOperations.replyOperations]);
return (
<div className="flex flex-col gap-2">
{repliesCount > 0 && !isExpanded && (
<Button
variant="link-neutral"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
className="flex items-center gap-2 text-xs text-custom-text-300 hover:text-custom-text-200 w-fit px-0 z-[1] ml-2 pl-3 relative"
>
{repliedUserIds.length > 0 && (
<AvatarGroup size="sm" max={2} showTooltip={false}>
{uniq(repliedUserIds).map((userId) => {
const userDetails = getUserDetails(userId);
if (!userDetails) return null;
return (
<Avatar
key={userId}
name={userDetails.display_name}
src={userDetails.avatar_url ? getFileURL(userDetails.avatar_url) : undefined}
/>
);
})}
</AvatarGroup>
<div className="flex flex-col">
{repliesCount > 0 && (
<>
{!isExpanded && (
<Button
variant="link-neutral"
size="sm"
onClick={() => setIsExpanded(true)}
className="flex items-center gap-2 text-xs text-custom-text-300 hover:text-custom-text-200 w-fit px-0 ml-2 pl-3 relative"
>
{repliedUserIds.length > 0 && (
<AvatarGroup size="sm" max={2} showTooltip={false}>
{uniq(repliedUserIds).map((userId) => {
const userDetails = getUserDetails(userId);
if (!userDetails) return null;
return (
<Avatar
key={userId}
name={userDetails.display_name}
src={userDetails.avatar_url ? getFileURL(userDetails.avatar_url) : undefined}
/>
);
})}
</AvatarGroup>
)}
<span>
{repliesCount} {repliesCount === 1 ? "reply" : "replies"}
</span>
<div className="size-1 rounded-full bg-custom-background-80" />
{lastReplyAt && (
<div className="flex gap-1">
<span className="text-custom-text-400">Last reply</span>
<span className="text-custom-text-400">{calculateTimeAgo(lastReplyAt)}</span>
</div>
)}
<div className="absolute left-0 top-0 h-4 w-2 border-l border-b rounded-bl-full border-custom-background-80" />
</Button>
)}
<span>
{repliesCount} {repliesCount === 1 ? "reply" : "replies"}
</span>
<div className="size-1 rounded-full bg-custom-background-80" />
{lastReplyAt && (
<>
<span className="text-custom-text-400">Last reply</span>
<span className="text-custom-text-400">{calculateTimeAgo(lastReplyAt)}</span>
</>
)}
<div className="absolute left-0 top-0 h-4 w-2 border-l border-b rounded-bl-full border-custom-background-80" />
</Button>
)}
{isExpanded && repliesCount > 0 && (
<RepliesList
workspaceSlug={workspaceSlug}
projectId={projectId}
entityId={entityId}
commentId={commentId}
activityOperations={activityOperations}
showAccessSpecifier={showAccessSpecifier}
setIsExpanded={setIsExpanded}
/>
<div
className={cn(
"grid transition-[grid-template-rows] duration-200 ease-out",
isExpanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
)}
>
<div className="overflow-hidden">
<RepliesList
workspaceSlug={workspaceSlug}
projectId={projectId}
entityId={entityId}
commentId={commentId}
activityOperations={activityOperations}
showAccessSpecifier={showAccessSpecifier}
setIsExpanded={setIsExpanded}
/>
</div>
</div>
</>
)}
{
<div
@@ -49,7 +49,7 @@ export const IssueActivityBlockComponent: FC<TIssueActivityBlockComponent> = (pr
}`}
ref={activityBlockRef}
>
<div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
<div className="absolute left-[13px] top-0 bottom-0 w-px bg-custom-background-80" aria-hidden />
<div
className={cn(
"flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden border-2 border-transparent flex justify-center items-center z-[4] bg-custom-background-80 text-custom-text-200",
@@ -71,7 +71,7 @@ export const IssueActivityWorklog: FC<TIssueActivityWorklog> = observer((props)
<div
className={`relative flex gap-3 ${ends === "top" ? `pb-2` : ends === "bottom" ? `pt-2` : `py-2`} ${!worklog?.description && "items-center"}`}
>
<div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
<div className="absolute left-[13px] top-0 bottom-0 w-px bg-custom-background-80" aria-hidden />
<div className="flex-shrink-0 relative w-7 h-7 rounded-full flex justify-center items-center z-10 bg-gray-500 text-white border border-white uppercase font-medium">
{currentUser?.member?.avatar_url && currentUser?.member?.avatar_url !== "" ? (
<img
@@ -25,6 +25,13 @@ export interface IRepliesStore {
commentId: string,
data: Partial<TIssueComment>
) => Promise<TIssueComment>;
updateReply: (
workspaceSlug: string,
projectId: string,
issueId: string,
replyId: string,
data: Partial<TIssueComment>
) => Promise<TIssueComment>;
deleteReply: (workspaceSlug: string, projectId: string, issueId: string, replyId: string) => Promise<void>;
}
@@ -42,10 +49,10 @@ export class RepliesStore implements IRepliesStore {
makeObservable(this, {
// observables
repliesMap: observable,
// actions
fetchReplies: action,
createReply: action,
updateReply: action,
deleteReply: action,
});
@@ -59,10 +66,17 @@ export class RepliesStore implements IRepliesStore {
const commentMap = this.repliesMap.get(commentId);
if (!commentMap) return undefined;
return Array.from(commentMap.keys());
// order ids by created_at
const orderedIds = Array.from(commentMap.keys()).sort((a, b) => {
const aCreatedAt = commentMap.get(a)?.created_at;
const bCreatedAt = commentMap.get(b)?.created_at;
if (!aCreatedAt || !bCreatedAt) return 0;
return new Date(aCreatedAt).getTime() - new Date(bCreatedAt).getTime();
});
return orderedIds;
});
// actions
fetchReplies = async (
workspaceSlug: string,
projectId: string,
@@ -151,6 +165,7 @@ export class RepliesStore implements IRepliesStore {
const oldValues = instance.asJSON;
// Optimistic update with the data being sent
runInAction(() => {
instance.update(data);
});
@@ -164,6 +179,13 @@ export class RepliesStore implements IRepliesStore {
data
);
runInAction(() => {
instance.update({
updated_at: updatedReply.updated_at,
edited_at: updatedReply.edited_at,
});
});
return updatedReply;
} catch (error) {
runInAction(() => {
+1
View File
@@ -47,6 +47,7 @@
"./popover": "./dist/popover/index.js",
"./portal": "./dist/portal/index.js",
"./scrollarea": "./dist/scrollarea/index.js",
"./separator": "./dist/separator/index.js",
"./skeleton": "./dist/skeleton/index.js",
"./switch": "./dist/switch/index.js",
"./tab-navigation": "./dist/tab-navigation/index.js",
@@ -1,4 +1,4 @@
import type {FC} from "react";
import type { FC } from "react";
import { AudioFileIcon, CodeFileIcon, DocumentFileIcon, ImageFileIcon, VideoFileIcon } from "../../icons/attachments";
const extensionToIconMap: Record<string, FC<{ className?: string }>> = {
@@ -6,7 +6,7 @@ import type { ISvgIcons } from "../type";
export const CommentReplyIcon: React.FC<ISvgIcons> = ({ color = "currentColor", ...rest }) => (
<IconWrapper color={color} {...rest}>
<path
d="M12 7.42481V3.8252C12 3.25494 11.9993 2.86462 11.9747 2.5625C11.9506 2.26796 11.9072 2.11207 11.8506 2.00098C11.7188 1.74225 11.5078 1.53124 11.2491 1.39941C11.138 1.34288 10.9821 1.29946 10.6875 1.27539C10.3854 1.25072 9.9951 1.25 9.42485 1.25H3.82524C3.25499 1.25 2.86467 1.25072 2.56254 1.27539C2.268 1.29946 2.11212 1.34288 2.00102 1.39941C1.7423 1.53124 1.53128 1.74225 1.39946 2.00098C1.34293 2.11207 1.2995 2.26796 1.27544 2.5625C1.25076 2.86462 1.25004 3.25495 1.25004 3.8252V7.95801C1.25004 8.6177 1.25553 8.82569 1.29692 8.98047C1.42406 9.45497 1.79507 9.82598 2.26958 9.95312C2.42436 9.99452 2.63235 10 3.29204 10C3.63707 10.0002 3.91704 10.2799 3.91704 10.625V11.9902L5.35844 10.8379C5.66347 10.5939 5.88163 10.415 6.13188 10.2871C6.3425 10.1795 6.56719 10.1013 6.79887 10.0537C7.07428 9.99719 7.35634 10 7.74712 10H9.42485C9.9951 10 10.3854 9.99928 10.6875 9.97461C10.9821 9.95054 11.138 9.90712 11.2491 9.85059C11.5078 9.71876 11.7188 9.50775 11.8506 9.24902C11.9072 9.13793 11.9506 8.98204 11.9747 8.6875C11.9993 8.38538 12 7.99506 12 7.42481ZM13.25 7.42481C13.25 7.97433 13.2505 8.42428 13.2207 8.78906C13.1903 9.16112 13.1252 9.49977 12.9639 9.81641C12.7122 10.3103 12.3104 10.7122 11.8165 10.9639C11.4998 11.1251 11.1612 11.1903 10.7891 11.2207C10.4243 11.2505 9.97437 11.25 9.42485 11.25H7.74712C7.30639 11.25 7.17224 11.2532 7.04985 11.2783C6.92869 11.3032 6.81138 11.3441 6.70122 11.4004C6.58992 11.4572 6.48314 11.5389 6.13872 11.8145L4.54887 13.0869C4.41993 13.1901 4.2891 13.2943 4.17583 13.3691C4.07215 13.4377 3.87768 13.5564 3.62602 13.5566C3.33426 13.5569 3.05808 13.4243 2.87602 13.1963C2.71896 12.9995 2.68999 12.7731 2.67876 12.6494C2.66648 12.5142 2.66704 12.3467 2.66704 12.1816V11.2441C2.39009 11.2365 2.15706 11.2169 1.94536 11.1602C1.03979 10.9174 0.332675 10.2103 0.0898885 9.30469C-0.00492448 8.95084 4.47304e-05 8.53775 4.47304e-05 7.95801V3.8252C4.47304e-05 3.27567 -0.000433179 2.82572 0.0293416 2.46094C0.0597401 2.08888 0.124899 1.75023 0.286178 1.43359C0.537845 0.939669 0.939713 0.537801 1.43364 0.286133C1.75027 0.124855 2.08892 0.0596958 2.46098 0.0292973C2.82576 -0.000477467 3.27572 4.62387e-07 3.82524 4.62387e-07H9.42485C9.97437 4.62387e-07 10.4243 -0.000477435 10.7891 0.0292973C11.1612 0.0596958 11.4998 0.124855 11.8165 0.286133C12.3104 0.537801 12.7122 0.939669 12.9639 1.43359C13.1252 1.75023 13.1903 2.08888 13.2207 2.46094C13.2505 2.82572 13.25 3.27567 13.25 3.8252V7.42481Z"
d="M13.375 8.79981V5.2002C13.375 4.62994 13.3743 4.23962 13.3497 3.9375C13.3256 3.64296 13.2822 3.48707 13.2256 3.37598C13.0938 3.11725 12.8828 2.90624 12.6241 2.77441C12.513 2.71788 12.3571 2.67446 12.0625 2.65039C11.7604 2.62572 11.3701 2.625 10.7998 2.625H5.20024C4.62999 2.625 4.23967 2.62572 3.93754 2.65039C3.643 2.67446 3.48712 2.71788 3.37602 2.77441C3.1173 2.90624 2.90628 3.11725 2.77446 3.37598C2.71793 3.48707 2.6745 3.64296 2.65044 3.9375C2.62576 4.23962 2.62504 4.62995 2.62504 5.2002V9.33301C2.62504 9.9927 2.63053 10.2007 2.67192 10.3555C2.79906 10.83 3.17007 11.201 3.64458 11.3281C3.79936 11.3695 4.00735 11.375 4.66704 11.375C5.01207 11.3752 5.29204 11.6549 5.29204 12V13.3652L6.73344 12.2129C7.03847 11.9689 7.25663 11.79 7.50688 11.6621C7.7175 11.5545 7.94219 11.4763 8.17387 11.4287C8.44928 11.3722 8.73134 11.375 9.12212 11.375H10.7998C11.3701 11.375 11.7604 11.3743 12.0625 11.3496C12.3571 11.3255 12.513 11.2821 12.6241 11.2256C12.8828 11.0938 13.0938 10.8827 13.2256 10.624C13.2822 10.5129 13.3256 10.357 13.3497 10.0625C13.3743 9.76038 13.375 9.37006 13.375 8.79981ZM14.625 8.79981C14.625 9.34933 14.6255 9.79928 14.5957 10.1641C14.5653 10.5361 14.5002 10.8748 14.3389 11.1914C14.0872 11.6853 13.6854 12.0872 13.1915 12.3389C12.8748 12.5001 12.5362 12.5653 12.1641 12.5957C11.7993 12.6255 11.3494 12.625 10.7998 12.625H9.12212C8.68139 12.625 8.54724 12.6282 8.42485 12.6533C8.30369 12.6782 8.18638 12.7191 8.07622 12.7754C7.96492 12.8322 7.85814 12.9139 7.51372 13.1895L5.92387 14.4619C5.79493 14.5651 5.6641 14.6693 5.55083 14.7441C5.44715 14.8127 5.25268 14.9314 5.00102 14.9316C4.70926 14.9319 4.43308 14.7993 4.25102 14.5713C4.09396 14.3745 4.06499 14.1481 4.05376 14.0244C4.04148 13.8892 4.04204 13.7217 4.04204 13.5566V12.6191C3.76509 12.6115 3.53206 12.5919 3.32036 12.5352C2.41479 12.2924 1.70767 11.5853 1.46489 10.6797C1.37008 10.3258 1.37504 9.91275 1.37504 9.33301V5.2002C1.37504 4.65067 1.37457 4.20072 1.40434 3.83594C1.43474 3.46388 1.4999 3.12523 1.66118 2.80859C1.91285 2.31467 2.31471 1.9128 2.80864 1.66113C3.12527 1.49985 3.46392 1.4347 3.83598 1.4043C4.20076 1.37452 4.65072 1.375 5.20024 1.375H10.7998C11.3494 1.375 11.7993 1.37452 12.1641 1.4043C12.5362 1.4347 12.8748 1.49986 13.1915 1.66113C13.6854 1.9128 14.0872 2.31467 14.3389 2.80859C14.5002 3.12523 14.5653 3.46388 14.5957 3.83594C14.6255 4.20072 14.625 4.65067 14.625 5.2002V8.79981Z"
fill={color}
/>
</IconWrapper>
+1
View File
@@ -0,0 +1 @@
export * from "./separator";
+7 -2
View File
@@ -11,7 +11,7 @@ interface SeparatorProps extends React.ComponentProps<typeof SeparatorPrimitive>
}
const Separator = React.forwardRef(function Separator(
{ orientation = "horizontal", ...props }: SeparatorProps,
{ orientation = "horizontal", className, ...props }: SeparatorProps,
ref: React.ForwardedRef<React.ElementRef<typeof SeparatorPrimitive>>
) {
return (
@@ -21,7 +21,12 @@ const Separator = React.forwardRef(function Separator(
data-slot="separator"
data-orientation={orientation}
{...props}
className={cn("bg-custom-border-200", "shrink-0", orientation === "horizontal" ? "h-px w-full" : "h-full w-px")}
className={cn(
"bg-custom-border-200",
"shrink-0",
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
className
)}
/>
);
});
+1
View File
@@ -26,6 +26,7 @@ export default defineConfig({
"src/popover/index.ts",
"src/portal/index.ts",
"src/scrollarea/index.ts",
"src/separator/index.ts",
"src/skeleton/index.ts",
"src/switch/index.ts",
"src/table/index.ts",
@@ -4,5 +4,6 @@ import type { TIssueComment } from "./issue_comment";
export type TCommentReplyOperations = {
fetchReplies: (commentId: string) => Promise<void>;
createReply: (commentId: string, data: Partial<TIssueComment>) => Promise<TIssueComment | undefined>;
updateReply: (replyId: string, data: Partial<TIssueComment>) => Promise<void>;
deleteReply: (replyId: string) => Promise<void>;
};