forked from github/plane
feat: issues tooltip , fix: ui improvement (#317)
* fix: ellipsis added to issue title * feat: toolttip added * feat: assignees tooltip added * fix: build fix
This commit is contained in:
parent
2cadb3784b
commit
d8c10b6bc0
@ -16,8 +16,10 @@ import {
|
|||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues/view-select";
|
} from "components/issues/view-select";
|
||||||
|
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "components/ui";
|
import { Tooltip, CustomMenu } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
@ -151,11 +153,20 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
||||||
<a className="group relative flex items-center gap-2">
|
<a className="group relative flex items-center gap-2">
|
||||||
{properties.key && (
|
{properties.key && (
|
||||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
<Tooltip
|
||||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
tooltipHeading="ID"
|
||||||
</span>
|
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
|
||||||
|
>
|
||||||
|
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||||
|
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<span>{issue.name}</span>
|
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||||
|
<span className="w-auto max-w-lg text-ellipsis overflow-hidden whitespace-nowrap">
|
||||||
|
{issue.name}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,7 +82,9 @@ export const MyIssuesListItem: React.FC<Props> = ({
|
|||||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span>{issue.name}</span>
|
<span className="w-[275px] md:w-[450px] lg:w-[600px] text-ellipsis overflow-hidden whitespace-nowrap">
|
||||||
|
{issue.name}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// ui
|
// ui
|
||||||
import { AssigneesList, Avatar } from "components/ui";
|
import { AssigneesList, Avatar, Tooltip } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -56,13 +56,26 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div>
|
<div>
|
||||||
<Listbox.Button>
|
<Listbox.Button>
|
||||||
<div
|
<Tooltip
|
||||||
className={`flex ${
|
tooltipHeading="Assignee"
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
tooltipContent={
|
||||||
} items-center gap-1 text-xs`}
|
issue.assignee_details.length > 0
|
||||||
|
? issue.assignee_details
|
||||||
|
.map((assingee) =>
|
||||||
|
assingee.first_name !== "" ? assingee.first_name : assingee.email
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
: "No Assignee"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<AssigneesList userIds={issue.assignees ?? []} />
|
<div
|
||||||
</div>
|
className={`flex ${
|
||||||
|
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
|
} items-center gap-1 text-xs`}
|
||||||
|
>
|
||||||
|
<AssigneesList userIds={issue.assignees ?? []} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// ui
|
// ui
|
||||||
import { CustomDatePicker } from "components/ui";
|
import { CustomDatePicker, Tooltip } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { findHowManyDaysLeft } from "helpers/date-time.helper";
|
import { findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
@ -12,25 +12,27 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ViewDueDateSelect: React.FC<Props> = ({ issue, partialUpdateIssue, isNotAllowed }) => (
|
export const ViewDueDateSelect: React.FC<Props> = ({ issue, partialUpdateIssue, isNotAllowed }) => (
|
||||||
<div
|
<Tooltip tooltipHeading="Due Date" tooltipContent={issue.target_date ?? "N/A"}>
|
||||||
className={`group relative ${
|
<div
|
||||||
issue.target_date === null
|
className={`group relative ${
|
||||||
? ""
|
issue.target_date === null
|
||||||
: issue.target_date < new Date().toISOString()
|
? ""
|
||||||
? "text-red-600"
|
: issue.target_date < new Date().toISOString()
|
||||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
? "text-red-600"
|
||||||
}`}
|
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||||
>
|
}`}
|
||||||
<CustomDatePicker
|
>
|
||||||
placeholder="N/A"
|
<CustomDatePicker
|
||||||
value={issue?.target_date}
|
placeholder="N/A"
|
||||||
onChange={(val) =>
|
value={issue?.target_date}
|
||||||
partialUpdateIssue({
|
onChange={(val) =>
|
||||||
target_date: val,
|
partialUpdateIssue({
|
||||||
})
|
target_date: val,
|
||||||
}
|
})
|
||||||
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
}
|
||||||
disabled={isNotAllowed}
|
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
||||||
/>
|
disabled={isNotAllowed}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "components/ui";
|
import { CustomSelect, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
import { getPriorityIcon } from "components/icons/priority-icon";
|
||||||
// types
|
// types
|
||||||
@ -24,12 +24,14 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
label={
|
label={
|
||||||
<span>
|
<Tooltip tooltipHeading="Priority" tooltipContent={issue.priority ?? "None"}>
|
||||||
{getPriorityIcon(
|
<span>
|
||||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
{getPriorityIcon(
|
||||||
"text-sm"
|
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||||
)}
|
"text-sm"
|
||||||
</span>
|
)}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
value={issue.state}
|
value={issue.state}
|
||||||
onChange={(data: string) => {
|
onChange={(data: string) => {
|
||||||
|
@ -5,7 +5,7 @@ import useSWR from "swr";
|
|||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/state.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "components/ui";
|
import { CustomSelect, Tooltip } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
@ -48,7 +48,16 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
backgroundColor: states?.find((s) => s.id === issue.state)?.color,
|
backgroundColor: states?.find((s) => s.id === issue.state)?.color,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{addSpaceIfCamelCase(states?.find((s) => s.id === issue.state)?.name ?? "")}
|
<Tooltip
|
||||||
|
tooltipHeading="State"
|
||||||
|
tooltipContent={addSpaceIfCamelCase(
|
||||||
|
states?.find((s) => s.id === issue.state)?.name ?? ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{addSpaceIfCamelCase(states?.find((s) => s.id === issue.state)?.name ?? "")}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
value={issue.state}
|
value={issue.state}
|
||||||
|
@ -184,16 +184,18 @@ export const SingleState: React.FC<Props> = ({
|
|||||||
Set as default
|
Set as default
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<Tooltip content="Cannot delete the default state." disabled={!state.default}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${state.default ? "cursor-not-allowed" : ""} grid place-items-center`}
|
className={`${state.default ? "cursor-not-allowed" : ""} grid place-items-center`}
|
||||||
onClick={handleDeleteState}
|
onClick={handleDeleteState}
|
||||||
disabled={state.default}
|
disabled={state.default}
|
||||||
>
|
>
|
||||||
|
<Tooltip tooltipContent="Cannot delete the default state." disabled={!state.default}>
|
||||||
<TrashIcon className="h-4 w-4 text-red-400" />
|
<TrashIcon className="h-4 w-4 text-red-400" />
|
||||||
</button>
|
</Tooltip>
|
||||||
</Tooltip>
|
</button>
|
||||||
|
|
||||||
<button type="button" className="grid place-items-center" onClick={handleEditState}>
|
<button type="button" className="grid place-items-center" onClick={handleEditState}>
|
||||||
<PencilSquareIcon className="h-4 w-4 text-gray-400" />
|
<PencilSquareIcon className="h-4 w-4 text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -18,7 +18,7 @@ type AvatarProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Avatar: React.FC<AvatarProps> = ({ user, index }) => (
|
export const Avatar: React.FC<AvatarProps> = ({ user, index }) => (
|
||||||
<div className={`relative z-[1] h-5 w-5 rounded-full ${index && index !== 0 ? "-ml-2.5" : ""}`}>
|
<div className={`relative h-5 w-5 rounded-full ${index && index !== 0 ? "-ml-2.5" : ""}`}>
|
||||||
{user && user.avatar && user.avatar !== "" ? (
|
{user && user.avatar && user.avatar !== "" ? (
|
||||||
<div
|
<div
|
||||||
className={`h-5 w-5 rounded-full border-2 ${
|
className={`h-5 w-5 rounded-full border-2 ${
|
||||||
|
@ -1,66 +1,40 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
|
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
direction?: "top" | "right" | "bottom" | "left";
|
tooltipHeading?: string;
|
||||||
content: string | React.ReactNode;
|
tooltipContent: string;
|
||||||
margin?: string;
|
position?: "top" | "right" | "bottom" | "left";
|
||||||
children: React.ReactNode;
|
children: JSX.Element;
|
||||||
className?: string;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Tooltip: React.FC<Props> = ({
|
export const Tooltip: React.FC<Props> = ({
|
||||||
content,
|
tooltipHeading,
|
||||||
direction = "top",
|
tooltipContent,
|
||||||
|
position = "top",
|
||||||
children,
|
children,
|
||||||
margin = "24px",
|
|
||||||
className = "",
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [active, setActive] = useState(false);
|
|
||||||
const [styleConfig, setStyleConfig] = useState(`top-[calc(-100%-${margin})]`);
|
|
||||||
let timeout: any;
|
|
||||||
|
|
||||||
const showToolTip = () => {
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
setActive(true);
|
|
||||||
}, 300);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideToolTip = () => {
|
|
||||||
clearInterval(timeout);
|
|
||||||
setActive(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tooltipStyles = {
|
|
||||||
top: "left-[50%] translate-x-[-50%] before:contents-[''] before:border-solid before:border-transparent before:h-0 before:w-0 before:absolute before:pointer-events-none before:border-[6px] before:left-[50%] before:ml-[calc(6px*-1)] before:top-full before:border-t-black",
|
|
||||||
|
|
||||||
right: "right-[-100%] top-[50%] translate-x-0 translate-y-[-50%]",
|
|
||||||
|
|
||||||
bottom:
|
|
||||||
"left-[50%] translate-x-[-50%] before:contents-[''] before:border-solid before:border-transparent before:h-0 before:w-0 before:absolute before:pointer-events-none before:border-[6px] before:left-[50%] before:ml-[calc(6px*-1)] before:bottom-full before:border-b-black",
|
|
||||||
|
|
||||||
left: "left-[-100%] top-[50%] translate-x-0 translate-y-[-50%]",
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const styleConfig = `${direction}-[calc(-100%-${margin})]`;
|
|
||||||
setStyleConfig(styleConfig);
|
|
||||||
}, [margin, direction]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative inline-block" onMouseEnter={showToolTip} onMouseLeave={hideToolTip}>
|
<Tooltip2
|
||||||
{children}
|
disabled={disabled}
|
||||||
{active && (
|
content={
|
||||||
<div
|
<div className="flex flex-col justify-center items-start gap-1 max-w-[600px] text-xs rounded-md bg-white p-2 shadow-md capitalize text-left">
|
||||||
className={`${className} ${
|
{tooltipHeading ? (
|
||||||
disabled ? "hidden" : ""
|
<>
|
||||||
} absolute p-[6px] text-xs z-20 rounded leading-1 text-white bg-black text-center w-max max-w-[300px]
|
<h5 className="font-medium">{tooltipHeading}</h5>
|
||||||
${tooltipStyles[direction]} ${styleConfig}`}
|
<p className="text-gray-700">{tooltipContent}</p>
|
||||||
>
|
</>
|
||||||
{content}
|
) : (
|
||||||
|
<p className="text-gray-700">{tooltipContent}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
</div>
|
position={position}
|
||||||
|
renderTarget={({ isOpen: isTooltipOpen, ref: eleRefernce, ...tooltipProps }) =>
|
||||||
|
React.cloneElement(children, { ref: eleRefernce, ...tooltipProps, ...children.props })
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blueprintjs/core": "^4.16.3",
|
||||||
|
"@blueprintjs/popover2": "^1.13.3",
|
||||||
"@headlessui/react": "^1.7.3",
|
"@headlessui/react": "^1.7.3",
|
||||||
"@heroicons/react": "^2.0.12",
|
"@heroicons/react": "^2.0.12",
|
||||||
"@remirror/core": "^2.0.11",
|
"@remirror/core": "^2.0.11",
|
||||||
@ -46,8 +48,8 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||||
"@typescript-eslint/parser": "^5.48.2",
|
"@typescript-eslint/parser": "^5.48.2",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"eslint-config-custom": "*",
|
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.31.0",
|
||||||
|
"eslint-config-custom": "*",
|
||||||
"eslint-config-next": "12.2.2",
|
"eslint-config-next": "12.2.2",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"tailwindcss": "^3.1.6",
|
"tailwindcss": "^3.1.6",
|
||||||
|
Loading…
Reference in New Issue
Block a user