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:
Anmol Singh Bhatia 2023-02-22 11:42:17 +05:30 committed by GitHub
parent 2cadb3784b
commit d8c10b6bc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 125 additions and 108 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>
); );

View File

@ -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) => {

View File

@ -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}

View File

@ -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>

View File

@ -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 ${

View File

@ -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 })
}
/>
); );
}; };

View File

@ -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",