fix: minor bugs and ux improvements (#322)

* fix: ellipsis added to issue title

* feat: toolttip added

* feat: assignees tooltip added

* fix: build fix

* fix: build fix

* fix: build error

* fix: minor bugs and ux improvements

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@caravel.tech>
This commit is contained in:
Aaryan Khandelwal 2023-02-23 10:54:54 +05:30 committed by GitHub
parent 702cfeb4ee
commit 92f717962c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 317 additions and 272 deletions

View File

@ -165,19 +165,15 @@ export const SingleBoardIssue: React.FC<Props> = ({
const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)
.then(() => {
setToastAlert({
type: "success",
title: "Issue link copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
});
};
useEffect(() => {
@ -201,14 +197,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover/card:opacity-100">
{type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={editIssue}>Edit</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={editIssue}>Edit issue</CustomMenu.MenuItem>
{type !== "issue" && removeIssue && (
<CustomMenu.MenuItem onClick={removeIssue}>
<>Remove from {type}</>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
Delete permanently
Delete issue
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy issue link</CustomMenu.MenuItem>
</CustomMenu>
@ -236,7 +232,6 @@ export const SingleBoardIssue: React.FC<Props> = ({
issue={issue}
partialUpdateIssue={partialUpdateIssue}
isNotAllowed={isNotAllowed}
position="left"
/>
)}
{properties.state && selectedGroup !== "state_detail.name" && (
@ -258,6 +253,24 @@ export const SingleBoardIssue: React.FC<Props> = ({
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
</div>
)}
{properties.labels && (
<div className="flex flex-wrap gap-1">
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs"
>
<span
className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
</span>
))}
</div>
)}
{properties.assignee && (
<ViewAssigneeSelect
issue={issue}

View File

@ -82,21 +82,6 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
Add Link
</Dialog.Title>
<div className="mt-2 space-y-3">
<div>
<Input
id="title"
label="Title"
name="title"
type="text"
placeholder="Enter title"
autoComplete="off"
error={errors.title}
register={register}
validations={{
required: "Title is required",
}}
/>
</div>
<div>
<Input
id="url"
@ -112,6 +97,21 @@ export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, onFormSubmit }
}}
/>
</div>
<div>
<Input
id="title"
label="Title"
name="title"
type="text"
placeholder="Enter title"
autoComplete="off"
error={errors.title}
register={register}
validations={{
required: "Title is required",
}}
/>
</div>
</div>
</div>
</div>

View File

@ -124,19 +124,15 @@ export const SingleListIssue: React.FC<Props> = ({
const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)
.then(() => {
setToastAlert({
type: "success",
title: "Issue link copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
});
};
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
@ -196,6 +192,24 @@ export const SingleListIssue: React.FC<Props> = ({
{issue.sub_issues_count} {issue.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
</div>
)}
{properties.labels && (
<div className="flex flex-wrap gap-1">
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs"
>
<span
className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
</span>
))}
</div>
)}
{properties.assignee && (
<ViewAssigneeSelect
issue={issue}
@ -205,14 +219,14 @@ export const SingleListIssue: React.FC<Props> = ({
)}
{type && !isNotAllowed && (
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={editIssue}>Edit</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={editIssue}>Edit issue</CustomMenu.MenuItem>
{type !== "issue" && removeIssue && (
<CustomMenu.MenuItem onClick={removeIssue}>
<>Remove from {type}</>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
Delete permanently
Delete issue
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy issue link</CustomMenu.MenuItem>
</CustomMenu>

View File

@ -2,6 +2,7 @@ import Link from "next/link";
// icons
import { LinkIcon, TrashIcon } from "@heroicons/react/24/outline";
import { ExternalLinkIcon } from "components/icons";
// helpers
import { timeAgo } from "helpers/date-time.helper";
// types
@ -26,9 +27,17 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
return (
<>
{links.map((link) => (
<div key={link.id} className="group relative">
<div key={link.id} className="relative">
{!isNotAllowed && (
<div className="absolute top-1.5 right-1.5 z-10 opacity-0 group-hover:opacity-100">
<div className="absolute top-1.5 right-1.5 z-10 flex items-center gap-1">
<Link href={link.url}>
<a
className="grid h-7 w-7 place-items-center rounded bg-gray-100 p-1 outline-none"
target="_blank"
>
<ExternalLinkIcon width="14" height="14" />
</a>
</Link>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded bg-gray-100 p-1 text-red-500 outline-none duration-300 hover:bg-red-50"
@ -38,16 +47,18 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, userAuth }
</button>
</div>
)}
<Link href={link.url} target="_blank">
<a className="group relative flex gap-2 rounded-md border bg-gray-100 p-2">
<Link href={link.url}>
<a className="relative flex gap-2 rounded-md border bg-gray-50 p-2" target="_blank">
<div className="mt-0.5">
<LinkIcon className="h-3.5 w-3.5" />
</div>
<div>
<h5>{link.title}</h5>
{/* <p className="mt-0.5 text-gray-500">
Added {timeAgo(link.created_at)} ago by {link.created_by_detail.email}
</p> */}
<h5 className="w-4/5">{link.title}</h5>
<p className="mt-0.5 text-gray-500">
Added {timeAgo(link.created_at)}
<br />
by {link.created_by_detail.email}
</p>
</div>
</a>
</Link>

View File

@ -70,19 +70,16 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = (props) => {
const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`)
.then(() => {
setToastAlert({
type: "success",
title: "Cycle link copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Cycle link copied to clipboard.",
});
});
};
return (
@ -99,11 +96,9 @@ export const SingleCycleCard: React.FC<TSingleStatProps> = (props) => {
</a>
</Link>
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy cycle link</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleEditCycle}>Edit cycle</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleDeleteCycle}>
Delete cycle permanently
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleDeleteCycle}>Delete cycle</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy cycle link</CustomMenu.MenuItem>
</CustomMenu>
</div>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 text-xs">

View File

@ -0,0 +1,38 @@
import React from "react";
import type { Props } from "./types";
export const ExternalLinkIcon: React.FC<Props> = ({
width = "24",
height = "24",
className,
color = "black",
}) => (
<svg
width={width}
height={height}
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 122.6 122.88"
>
<g>
<path
d="M110.6,72.58c0-3.19,2.59-5.78,5.78-5.78c3.19,0,5.78,2.59,5.78,5.78v33.19c0,4.71-1.92,8.99-5.02,12.09 c-3.1,3.1-7.38,5.02-12.09,5.02H17.11c-4.71,0-8.99-1.92-12.09-5.02c-3.1-3.1-5.02-7.38-5.02-12.09V17.19 C0,12.48,1.92,8.2,5.02,5.1C8.12,2,12.4,0.08,17.11,0.08h32.98c3.19,0,5.78,2.59,5.78,5.78c0,3.19-2.59,5.78-5.78,5.78H17.11 c-1.52,0-2.9,0.63-3.91,1.63c-1.01,1.01-1.63,2.39-1.63,3.91v88.58c0,1.52,0.63,2.9,1.63,3.91c1.01,1.01,2.39,1.63,3.91,1.63h87.95 c1.52,0,2.9-0.63,3.91-1.63s1.63-2.39,1.63-3.91V72.58L110.6,72.58z M112.42,17.46L54.01,76.6c-2.23,2.27-5.89,2.3-8.16,0.07 c-2.27-2.23-2.3-5.89-0.07-8.16l56.16-56.87H78.56c-3.19,0-5.78-2.59-5.78-5.78c0-3.19,2.59-5.78,5.78-5.78h26.5 c5.12,0,11.72-0.87,15.65,3.1c2.48,2.51,1.93,22.52,1.61,34.11c-0.08,3-0.15,5.29-0.15,6.93c0,3.19-2.59,5.78-5.78,5.78 c-3.19,0-5.78-2.59-5.78-5.78c0-0.31,0.08-3.32,0.19-7.24C110.96,30.94,111.93,22.94,112.42,17.46L112.42,17.46z"
fill={color}
/>
</g>
</svg>
// <svg
// width={width}
// height={height}
// className={className}
// viewBox="0 0 24 24"
// fill="none"
// xmlns="http://www.w3.org/2000/svg"
// >
// <path
// d="M5.2 13.1998C4.86667 13.1998 4.58333 13.0831 4.35 12.8498C4.11667 12.6165 4 12.3331 4 11.9998C4 11.6665 4.11667 11.3831 4.35 11.1498C4.58333 10.9165 4.86667 10.7998 5.2 10.7998C5.53333 10.7998 5.81667 10.9165 6.05 11.1498C6.28333 11.3831 6.4 11.6665 6.4 11.9998C6.4 12.3331 6.28333 12.6165 6.05 12.8498C5.81667 13.0831 5.53333 13.1998 5.2 13.1998ZM12 13.1998C11.6667 13.1998 11.3833 13.0831 11.15 12.8498C10.9167 12.6165 10.8 12.3331 10.8 11.9998C10.8 11.6665 10.9167 11.3831 11.15 11.1498C11.3833 10.9165 11.6667 10.7998 12 10.7998C12.3333 10.7998 12.6167 10.9165 12.85 11.1498C13.0833 11.3831 13.2 11.6665 13.2 11.9998C13.2 12.3331 13.0833 12.6165 12.85 12.8498C12.6167 13.0831 12.3333 13.1998 12 13.1998ZM18.8 13.1998C18.4667 13.1998 18.1833 13.0831 17.95 12.8498C17.7167 12.6165 17.6 12.3331 17.6 11.9998C17.6 11.6665 17.7167 11.3831 17.95 11.1498C18.1833 10.9165 18.4667 10.7998 18.8 10.7998C19.1333 10.7998 19.4167 10.9165 19.65 11.1498C19.8833 11.3831 20 11.6665 20 11.9998C20 12.3331 19.8833 12.6165 19.65 12.8498C19.4167 13.0831 19.1333 13.1998 18.8 13.1998Z"
// fill="black"
// />
// </svg>
);

View File

@ -1,19 +1,26 @@
export * from "./attachment-icon";
export * from "./blocked-icon";
export * from "./blocker-icon";
export * from "./bolt-icon";
export * from "./calendar-month-icon";
export * from "./cancel-icon";
export * from "./clipboard-icon";
export * from "./comment-icon";
export * from "./completed-cycle-icon";
export * from "./current-cycle-icon";
export * from "./cycle-icon";
export * from "./discord-icon";
export * from "./document-icon";
export * from "./edit-icon";
export * from "./ellipsis-horizontal-icon";
export * from "./external-link-icon";
export * from "./github-icon";
export * from "./heartbeat-icon";
export * from "./layer-diagonal-icon";
export * from "./lock-icon";
export * from "./menu-icon";
export * from "./plus-icon";
export * from "./question-mark-circle-icon";
export * from "./setting-icon";
export * from "./signal-cellular-icon";
export * from "./tag-icon";
@ -22,9 +29,3 @@ export * from "./upcoming-cycle-icon";
export * from "./user-group-icon";
export * from "./user-icon-circle";
export * from "./user-icon";
export * from "./question-mark-circle-icon";
export * from "./bolt-icon";
export * from "./document-icon";
export * from "./discord-icon";
export * from "./github-icon";
export * from "./comment-icon";

View File

@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useMemo } from "react";
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import dynamic from "next/dynamic";
@ -18,7 +18,6 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor
});
// types
import { IIssue, UserAuth } from "types";
import useToast from "hooks/use-toast";
export interface IssueDescriptionFormValues {
name: string;
@ -37,7 +36,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
handleFormSubmit,
userAuth,
}) => {
const { setToastAlert } = useToast();
const [characterLimit, setCharacterLimit] = useState(false);
const {
handleSubmit,
@ -55,23 +54,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
const handleDescriptionFormSubmit = useCallback(
(formData: Partial<IIssue>) => {
if (!formData.name || formData.name === "") {
setToastAlert({
type: "error",
title: "Error in saving!",
message: "Title is required.",
});
return;
}
if (formData.name.length > 255) {
setToastAlert({
type: "error",
title: "Error in saving!",
message: "Title cannot have more than 255 characters.",
});
return;
}
if (!formData.name || formData.name.length === 0 || formData.name.length > 255) return;
handleFormSubmit({
name: formData.name ?? "",
@ -79,7 +62,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
description_html: formData.description_html ?? "<p></p>",
});
},
[handleFormSubmit, setToastAlert]
[handleFormSubmit]
);
const debounceHandler = useMemo(
@ -105,21 +88,37 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
return (
<div>
<TextArea
id="name"
placeholder="Enter issue name"
name="name"
value={watch("name")}
onChange={(e) => {
setValue("name", e.target.value);
debounceHandler();
}}
required={true}
className="block px-3 py-2 text-xl
<div className="relative">
<TextArea
id="name"
name="name"
placeholder="Enter issue name"
value={watch("name")}
onFocus={() => setCharacterLimit(true)}
onBlur={() => setCharacterLimit(false)}
onChange={(e) => {
setValue("name", e.target.value);
debounceHandler();
}}
required={true}
className="block px-3 py-2 text-xl
w-full overflow-hidden resize-none min-h-10
rounded border-none bg-transparent ring-0 focus:ring-1 focus:ring-theme outline-none "
role="textbox "
/>
rounded border-none bg-transparent ring-0 focus:ring-1 focus:ring-theme outline-none"
role="textbox"
/>
{characterLimit && (
<div className="absolute bottom-0 right-0 text-xs bg-white p-1 rounded pointer-events-none z-[2]">
<span
className={`${
watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""
}`}
>
{watch("name").length}
</span>
/255
</div>
)}
</div>
<span>{errors.name ? errors.name.message : null}</span>
<RemirrorRichTextEditor
value={watch("description")}

View File

@ -45,6 +45,9 @@ const defaultValues: Partial<IIssue> = {
state: "",
cycle: null,
priority: null,
assignees: [],
assignees_list: [],
labels: [],
labels_list: [],
};
@ -89,6 +92,7 @@ export const IssueForm: FC<IssueFormProps> = ({
watch,
control,
setValue,
setFocus,
} = useForm<IIssue>({
defaultValues,
mode: "all",
@ -113,12 +117,14 @@ export const IssueForm: FC<IssueFormProps> = ({
};
useEffect(() => {
setFocus("name");
reset({
...defaultValues,
...initialData,
project: projectId,
});
}, [initialData, reset, projectId]);
}, [setFocus, initialData, reset, projectId]);
return (
<>
@ -204,13 +210,13 @@ export const IssueForm: FC<IssueFormProps> = ({
<div className="flex items-center gap-x-2">
<p className="text-sm text-gray-500">
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${mostSimilarIssue}`}
href={`/${workspaceSlug}/projects/${projectId}/issues/${mostSimilarIssue.id}`}
>
<a target="_blank" type="button" className="inline text-left">
<span>Did you mean </span>
<span className="italic">
{mostSimilarIssue?.project_detail.identifier}-
{mostSimilarIssue?.sequence_id}: {mostSimilarIssue?.name}{" "}
{mostSimilarIssue.project_detail.identifier}-
{mostSimilarIssue.sequence_id}: {mostSimilarIssue.name}{" "}
</span>
?
</a>
@ -363,7 +369,7 @@ export const IssueForm: FC<IssueFormProps> = ({
</button>
</div>
<div className="flex items-center gap-2">
<Button theme="secondary" onClick={handleClose}>
<Button type="button" theme="secondary" onClick={handleClose}>
Discard
</Button>
<Button type="submit" disabled={isSubmitting}>

View File

@ -115,6 +115,24 @@ export const MyIssuesListItem: React.FC<Props> = ({
{issue?.sub_issues_count} {issue?.sub_issues_count === 1 ? "sub-issue" : "sub-issues"}
</div>
)}
{properties.labels && (
<div className="flex flex-wrap gap-1">
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border px-2 py-0.5 text-xs"
>
<span
className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
style={{
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
</span>
))}
</div>
)}
{properties.assignee && (
<div className="flex items-center gap-1">
<AssigneesList userIds={issue.assignees ?? []} />

View File

@ -188,6 +188,21 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
});
};
const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issueDetail?.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Issue link copied to clipboard.",
});
});
};
useEffect(() => {
if (!createLabelForm) return;
@ -217,23 +232,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<button
type="button"
className="rounded-md border p-2 shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
onClick={() =>
copyTextToClipboard(
`https://app.plane.so/${workspaceSlug}/projects/${issueDetail?.project_detail?.id}/issues/${issueDetail?.id}`
)
.then(() => {
setToastAlert({
type: "success",
title: "Issue link copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
})
}
onClick={handleCopyText}
>
<LinkIcon className="h-3.5 w-3.5" />
</button>
@ -373,7 +372,10 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
>
<span
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{ backgroundColor: label?.color ?? "black" }}
style={{
backgroundColor:
label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
<XMarkIcon className="h-2 w-2 group-hover:text-red-500" />

View File

@ -203,12 +203,12 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
>
<Disclosure.Panel className="mt-3 flex flex-col gap-y-1">
{subIssues.map((issue) => (
<div
<Link
key={issue.id}
className="group flex items-center justify-between gap-2 rounded p-2 hover:bg-gray-100"
href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}
>
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`}>
<a className="flex items-center gap-2 rounded text-xs">
<a className="group flex items-center justify-between gap-2 rounded p-2 hover:bg-gray-100">
<div className="flex items-center gap-2 rounded text-xs">
<span
className="block flex-shrink-0 h-1.5 w-1.5 rounded-full"
style={{
@ -219,18 +219,23 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
{issue.project_detail.identifier}-{issue.sequence_id}
</span>
<span className="max-w-sm break-all font-medium">{issue.name}</span>
</a>
</Link>
{!isNotAllowed && (
<button
type="button"
className="opacity-0 group-hover:opacity-100 cursor-pointer"
onClick={() => handleSubIssueRemove(issue.id)}
>
<XMarkIcon className="h-4 w-4 text-gray-500 hover:text-gray-900" />
</button>
)}
</div>
</div>
{!isNotAllowed && (
<button
type="button"
className="opacity-0 group-hover:opacity-100 cursor-pointer"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleSubIssueRemove(issue.id);
}}
>
<XMarkIcon className="h-4 w-4 text-gray-500 hover:text-gray-900" />
</button>
)}
</a>
</Link>
))}
</Disclosure.Panel>
</Transition>

View File

@ -57,14 +57,14 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
<div>
<Listbox.Button>
<Tooltip
tooltipHeading="Assignee"
tooltipHeading="Assignees"
tooltipContent={
issue.assignee_details.length > 0
? issue.assignee_details
.map((assingee) =>
assingee.first_name !== "" ? assingee.first_name : assingee.email
.map((assignee) =>
assignee?.first_name !== "" ? assignee?.first_name : assignee?.email
)
.toString()
.join(", ")
: "No Assignee"
}
>

View File

@ -202,7 +202,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
/>
</div>
<div className="flex justify-center items-center gap-2 rounded-md border bg-transparent h-full p-2 px-4 text-xs font-medium text-gray-900 hover:bg-gray-100 hover:text-gray-900 focus:outline-none">
<Popover className="flex justify-center items-center relative rounded-lg">
<Popover className="flex justify-center items-center relative rounded-lg">
{({ open }) => (
<>
<Popover.Button
@ -372,7 +372,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
</div>
</div>
</div>
<div className="flex flex-col items-center justify-center w-full gap-2 ">
<div className="flex flex-col items-center justify-center w-full gap-2">
{isStartValid && isEndValid ? (
<ProgressChart
issues={issues}

View File

@ -8,7 +8,7 @@ import { DeleteModuleModal } from "components/modules";
// ui
import { AssigneesList, Avatar, CustomMenu } from "components/ui";
// icons
import { CalendarDaysIcon, TrashIcon } from "@heroicons/react/24/outline";
import { CalendarDaysIcon } from "@heroicons/react/24/outline";
// helpers
import { renderShortNumericDateFormat } from "helpers/date-time.helper";
// types
@ -39,19 +39,16 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule })
const handleCopyText = () => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/modules/${module.id}`)
.then(() => {
setToastAlert({
type: "success",
title: "Module link copied to clipboard",
});
})
.catch(() => {
setToastAlert({
type: "error",
title: "Some error occurred",
});
copyTextToClipboard(
`${originURL}/${workspaceSlug}/projects/${projectId}/modules/${module.id}`
).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Module link copied to clipboard.",
});
});
};
return (
@ -64,11 +61,9 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule })
<div className="group/card h-full w-full relative select-none p-2">
<div className="absolute top-4 right-4 ">
<CustomMenu width="auto" ellipsis>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy module link</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleEditModule}>Edit module</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleDeleteModule}>
Delete module permanently
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleDeleteModule}>Delete module</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={handleCopyText}>Copy module link</CustomMenu.MenuItem>
</CustomMenu>
</div>
<Link href={`/${workspaceSlug}/projects/${module.project}/modules/${module.id}`}>

View File

@ -66,6 +66,18 @@ export const ProjectSidebarList: FC = () => {
() => (workspaceSlug ? projectService.getProjects(workspaceSlug as string) : null)
);
const handleCopyText = (projectId: string) => {
const originURL =
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues`).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "Project link copied to clipboard.",
});
});
};
return (
<>
<CreateProjectModal isOpen={isCreateProjectModal} setIsOpen={setCreateProjectModal} />
@ -112,20 +124,8 @@ export const ProjectSidebarList: FC = () => {
</Disclosure.Button>
{!sidebarCollapse && (
<CustomMenu ellipsis>
<CustomMenu.MenuItem
onClick={() =>
copyTextToClipboard(
`https://app.plane.so/${workspaceSlug}/projects/${project?.id}/issues/`
).then(() => {
setToastAlert({
title: "Link Copied",
message: "Link copied to clipboard",
type: "success",
});
})
}
>
Copy link
<CustomMenu.MenuItem onClick={() => handleCopyText(project.id)}>
Copy project link
</CustomMenu.MenuItem>
</CustomMenu>
)}

View File

@ -4,14 +4,13 @@ import {
ToggleItalicButton,
ToggleUnderlineButton,
ToggleStrikeButton,
ToggleOrderedListButton,
ToggleBulletListButton,
RedoButton,
UndoButton,
} from "@remirror/react";
// headings
import HeadingControls from "./heading-controls";
// list
import { OrderedListButton } from "./ordered-list";
import { UnorderedListButton } from "./unordered-list";
export const RichTextToolbar: React.FC = () => (
<div className="flex items-center gap-y-2 divide-x">
@ -29,11 +28,8 @@ export const RichTextToolbar: React.FC = () => (
<ToggleStrikeButton />
</div>
<div className="flex items-center gap-x-1 px-2">
<OrderedListButton />
<UnorderedListButton />
<ToggleOrderedListButton />
<ToggleBulletListButton />
</div>
{/* <div className="flex items-center gap-x-1 px-2">
<LinkButton />
</div> */}
</div>
);

View File

@ -138,8 +138,10 @@ const DelayAutoFocusInput = ({ autoFocus, ...rest }: HTMLProps<HTMLInputElement>
export const FloatingLinkToolbar = () => {
const { isEditing, linkPositioner, clickEdit, onRemove, submitHref, href, setHref, cancelHref } =
useFloatingLinkState();
const active = useActive();
const activeLink = active.link();
const { empty } = useCurrentSelection();
const handleClickEdit = useCallback(() => {
@ -148,6 +150,14 @@ export const FloatingLinkToolbar = () => {
const linkEditButtons = activeLink ? (
<>
<CommandButton
commandName="openLink"
onSelect={() => {
window.open(href, "_blank");
}}
icon="externalLinkFill"
enabled
/>
<CommandButton
commandName="updateLink"
onSelect={handleClickEdit}
@ -164,7 +174,9 @@ export const FloatingLinkToolbar = () => {
<>
{!isEditing && <FloatingToolbar>{linkEditButtons}</FloatingToolbar>}
{!isEditing && empty && (
<FloatingToolbar positioner={linkPositioner}>{linkEditButtons}</FloatingToolbar>
<FloatingToolbar positioner={linkPositioner} className="shadow-lg rounded bg-white p-1">
{linkEditButtons}
</FloatingToolbar>
)}
<FloatingWrapper

View File

@ -1,28 +0,0 @@
import { useCommands, useActive } from "@remirror/react";
export const OrderedListButton = () => {
const { toggleOrderedList, focus } = useCommands();
const active = useActive();
return (
<button
type="button"
onClick={() => {
toggleOrderedList();
focus();
}}
className={`${active.orderedList() ? "bg-gray-200" : "hover:bg-gray-100"} rounded p-1`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 48 48"
fill="black"
>
<path d="M6 40v-1.7h4.2V37H8.1v-1.7h2.1V34H6v-1.7h5.9V40Zm10.45-2.45v-3H42v3ZM6 27.85v-1.6l3.75-4.4H6v-1.7h5.9v1.6l-3.8 4.4h3.8v1.7Zm10.45-2.45v-3H42v3ZM8.1 15.8V9.7H6V8h3.8v7.8Zm8.35-2.55v-3H42v3Z" />
</svg>
</button>
);
};

View File

@ -1,28 +0,0 @@
import { useCommands, useActive } from "@remirror/react";
export const UnorderedListButton = () => {
const { toggleBulletList, focus } = useCommands();
const active = useActive();
return (
<button
type="button"
onClick={() => {
toggleBulletList();
focus();
}}
className={`${active.bulletList() ? "bg-gray-200" : "hover:bg-gray-100"} rounded p-1`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="18"
width="18"
fill="black"
viewBox="0 0 48 48"
>
<path d="M8.55 39q-1.05 0-1.8-.725T6 36.55q0-1.05.75-1.8t1.8-.75q1 0 1.725.75.725.75.725 1.8 0 1-.725 1.725Q9.55 39 8.55 39ZM16 38v-3h26v3ZM8.55 26.5q-1.05 0-1.8-.725T6 24q0-1.05.75-1.775.75-.725 1.8-.725 1 0 1.725.75Q11 23 11 24t-.725 1.75q-.725.75-1.725.75Zm7.45-1v-3h26v3ZM8.5 14q-1.05 0-1.775-.725Q6 12.55 6 11.5q0-1.05.725-1.775Q7.45 9 8.5 9q1.05 0 1.775.725Q11 10.45 11 11.5q0 1.05-.725 1.775Q9.55 14 8.5 14Zm7.5-1v-3h26v3Z" />
</svg>
</button>
);
};

View File

@ -136,11 +136,7 @@ export const SingleState: React.FC<Props> = ({
};
return (
<div
className={`group flex items-center justify-between gap-2 border-b bg-gray-50 p-3 ${
activeGroup !== state.group ? "last:border-0" : ""
}`}
>
<div className="group flex items-center justify-between gap-2 bg-gray-50 p-3">
<div className="flex items-center gap-2">
<span
className="h-3 w-3 flex-shrink-0 rounded-full"

View File

@ -8,12 +8,12 @@ import useUser from "hooks/use-user";
import { IssuePriorities, Properties } from "types";
const initialValues: Properties = {
key: true,
state: true,
assignee: true,
priority: false,
due_date: false,
// cycle: false,
key: true,
labels: false,
priority: false,
state: true,
sub_issue_count: false,
};
@ -83,12 +83,12 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
);
const newProperties: Properties = {
key: properties.key,
state: properties.state,
assignee: properties.assignee,
priority: properties.priority,
due_date: properties.due_date,
// cycle: properties.cycle,
key: properties.key,
labels: properties.labels,
priority: properties.priority,
state: properties.state,
sub_issue_count: properties.sub_issue_count,
};

View File

@ -17,12 +17,12 @@ import { STATE_LIST } from "constants/fetch-keys";
import { PRIORITIES } from "constants/project";
const initialValues: Properties = {
key: true,
state: true,
assignee: true,
priority: false,
due_date: false,
// cycle: false,
key: true,
labels: true,
priority: false,
state: true,
sub_issue_count: false,
};

View File

@ -99,7 +99,7 @@ const StatesSettings: NextPage<UserAuth> = (props) => {
Add
</button>
</div>
<div className="space-y-1 rounded-xl border p-1 md:w-2/3">
<div className="space-y-1 rounded-xl border divide-y p-1 md:w-2/3">
{key === activeGroup && (
<CreateUpdateStateInline
onClose={() => {

View File

@ -161,12 +161,12 @@ export type IssuePriorities = {
};
export type Properties = {
key: boolean;
state: boolean;
assignee: boolean;
priority: boolean;
due_date: boolean;
// cycle: boolean;
labels: boolean;
key: boolean;
priority: boolean;
state: boolean;
sub_issue_count: boolean;
};