mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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,
|
||||
ViewStateSelect,
|
||||
} from "components/issues/view-select";
|
||||
import { Tooltip2 } from "@blueprintjs/popover2";
|
||||
|
||||
// ui
|
||||
import { CustomMenu } from "components/ui";
|
||||
import { Tooltip, CustomMenu } from "components/ui";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
@ -151,11 +153,20 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
||||
<a className="group relative flex items-center gap-2">
|
||||
{properties.key && (
|
||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<Tooltip
|
||||
tooltipHeading="ID"
|
||||
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>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -82,7 +82,9 @@ export const MyIssuesListItem: React.FC<Props> = ({
|
||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||
</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>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ import { Listbox, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import projectService from "services/project.service";
|
||||
// ui
|
||||
import { AssigneesList, Avatar } from "components/ui";
|
||||
import { AssigneesList, Avatar, Tooltip } from "components/ui";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// fetch-keys
|
||||
@ -56,13 +56,26 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
||||
{({ open }) => (
|
||||
<div>
|
||||
<Listbox.Button>
|
||||
<div
|
||||
className={`flex ${
|
||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||
} items-center gap-1 text-xs`}
|
||||
<Tooltip
|
||||
tooltipHeading="Assignee"
|
||||
tooltipContent={
|
||||
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>
|
||||
|
||||
<Transition
|
||||
|
@ -1,5 +1,5 @@
|
||||
// ui
|
||||
import { CustomDatePicker } from "components/ui";
|
||||
import { CustomDatePicker, Tooltip } from "components/ui";
|
||||
// helpers
|
||||
import { findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
// types
|
||||
@ -12,25 +12,27 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ViewDueDateSelect: React.FC<Props> = ({ issue, partialUpdateIssue, isNotAllowed }) => (
|
||||
<div
|
||||
className={`group relative ${
|
||||
issue.target_date === null
|
||||
? ""
|
||||
: issue.target_date < new Date().toISOString()
|
||||
? "text-red-600"
|
||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||
}`}
|
||||
>
|
||||
<CustomDatePicker
|
||||
placeholder="N/A"
|
||||
value={issue?.target_date}
|
||||
onChange={(val) =>
|
||||
partialUpdateIssue({
|
||||
target_date: val,
|
||||
})
|
||||
}
|
||||
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
||||
disabled={isNotAllowed}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Due Date" tooltipContent={issue.target_date ?? "N/A"}>
|
||||
<div
|
||||
className={`group relative ${
|
||||
issue.target_date === null
|
||||
? ""
|
||||
: issue.target_date < new Date().toISOString()
|
||||
? "text-red-600"
|
||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||
}`}
|
||||
>
|
||||
<CustomDatePicker
|
||||
placeholder="N/A"
|
||||
value={issue?.target_date}
|
||||
onChange={(val) =>
|
||||
partialUpdateIssue({
|
||||
target_date: val,
|
||||
})
|
||||
}
|
||||
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
||||
disabled={isNotAllowed}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
// ui
|
||||
import { CustomSelect } from "components/ui";
|
||||
import { CustomSelect, Tooltip } from "components/ui";
|
||||
// icons
|
||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
||||
// types
|
||||
@ -24,12 +24,14 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
||||
}) => (
|
||||
<CustomSelect
|
||||
label={
|
||||
<span>
|
||||
{getPriorityIcon(
|
||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||
"text-sm"
|
||||
)}
|
||||
</span>
|
||||
<Tooltip tooltipHeading="Priority" tooltipContent={issue.priority ?? "None"}>
|
||||
<span>
|
||||
{getPriorityIcon(
|
||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||
"text-sm"
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
value={issue.state}
|
||||
onChange={(data: string) => {
|
||||
|
@ -5,7 +5,7 @@ import useSWR from "swr";
|
||||
// services
|
||||
import stateService from "services/state.service";
|
||||
// ui
|
||||
import { CustomSelect } from "components/ui";
|
||||
import { CustomSelect, Tooltip } from "components/ui";
|
||||
// helpers
|
||||
import { addSpaceIfCamelCase } from "helpers/string.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,
|
||||
}}
|
||||
/>
|
||||
{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}
|
||||
|
@ -184,16 +184,18 @@ export const SingleState: React.FC<Props> = ({
|
||||
Set as default
|
||||
</button>
|
||||
)}
|
||||
<Tooltip content="Cannot delete the default state." disabled={!state.default}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${state.default ? "cursor-not-allowed" : ""} grid place-items-center`}
|
||||
onClick={handleDeleteState}
|
||||
disabled={state.default}
|
||||
>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`${state.default ? "cursor-not-allowed" : ""} grid place-items-center`}
|
||||
onClick={handleDeleteState}
|
||||
disabled={state.default}
|
||||
>
|
||||
<Tooltip tooltipContent="Cannot delete the default state." disabled={!state.default}>
|
||||
<TrashIcon className="h-4 w-4 text-red-400" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
</button>
|
||||
|
||||
<button type="button" className="grid place-items-center" onClick={handleEditState}>
|
||||
<PencilSquareIcon className="h-4 w-4 text-gray-400" />
|
||||
</button>
|
||||
|
@ -18,7 +18,7 @@ type AvatarProps = {
|
||||
};
|
||||
|
||||
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 !== "" ? (
|
||||
<div
|
||||
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 = {
|
||||
direction?: "top" | "right" | "bottom" | "left";
|
||||
content: string | React.ReactNode;
|
||||
margin?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
tooltipHeading?: string;
|
||||
tooltipContent: string;
|
||||
position?: "top" | "right" | "bottom" | "left";
|
||||
children: JSX.Element;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const Tooltip: React.FC<Props> = ({
|
||||
content,
|
||||
direction = "top",
|
||||
tooltipHeading,
|
||||
tooltipContent,
|
||||
position = "top",
|
||||
children,
|
||||
margin = "24px",
|
||||
className = "",
|
||||
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 (
|
||||
<div className="relative inline-block" onMouseEnter={showToolTip} onMouseLeave={hideToolTip}>
|
||||
{children}
|
||||
{active && (
|
||||
<div
|
||||
className={`${className} ${
|
||||
disabled ? "hidden" : ""
|
||||
} absolute p-[6px] text-xs z-20 rounded leading-1 text-white bg-black text-center w-max max-w-[300px]
|
||||
${tooltipStyles[direction]} ${styleConfig}`}
|
||||
>
|
||||
{content}
|
||||
<Tooltip2
|
||||
disabled={disabled}
|
||||
content={
|
||||
<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">
|
||||
{tooltipHeading ? (
|
||||
<>
|
||||
<h5 className="font-medium">{tooltipHeading}</h5>
|
||||
<p className="text-gray-700">{tooltipContent}</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-gray-700">{tooltipContent}</p>
|
||||
)}
|
||||
</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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^4.16.3",
|
||||
"@blueprintjs/popover2": "^1.13.3",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@remirror/core": "^2.0.11",
|
||||
@ -46,8 +48,8 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint-config-custom": "*",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-custom": "*",
|
||||
"eslint-config-next": "12.2.2",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.6",
|
||||
|
Loading…
Reference in New Issue
Block a user