[WEB-3892] chore: link item improvements (#6944)
* chore: code refactor * chore: global link block component added * chore: link item improvement and code refactor
This commit is contained in:
parent
18fb3b8450
commit
be5d77d978
8 changed files with 210 additions and 212 deletions
|
|
@ -1,21 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { FC, useCallback, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import {
|
||||
Pencil,
|
||||
ExternalLink,
|
||||
Link,
|
||||
Trash2
|
||||
} from "lucide-react";
|
||||
import { Pencil, ExternalLink, Link, Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
import { TOAST_TYPE, setToast, TContextMenuItem, LinkItemBlock } from "@plane/ui";
|
||||
// plane utils
|
||||
import { cn, copyTextToClipboard,getIconForLink } from "@plane/utils";
|
||||
|
||||
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@plane/utils";
|
||||
// hooks
|
||||
import { useHome } from "@/hooks/store/use-home";
|
||||
// types
|
||||
|
|
@ -34,101 +25,76 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
|||
quickLinks: { getLinkById, toggleLinkModal, setLinkData },
|
||||
} = useHome();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// derived values
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
const viewLink = linkDetail.url;
|
||||
|
||||
const Icon = getIconForLink(linkDetail.url);
|
||||
if (!linkDetail) return null;
|
||||
|
||||
const handleEdit = (modalToggle: boolean) => {
|
||||
toggleLinkModal(modalToggle);
|
||||
setLinkData(linkDetail);
|
||||
};
|
||||
// handlers
|
||||
const handleEdit = useCallback(
|
||||
(modalToggle: boolean) => {
|
||||
toggleLinkModal(modalToggle);
|
||||
setLinkData(linkDetail);
|
||||
},
|
||||
[linkDetail, setLinkData, toggleLinkModal]
|
||||
);
|
||||
|
||||
const handleCopyText = () =>
|
||||
copyTextToClipboard(viewLink).then(() => {
|
||||
const handleCopyText = useCallback(() => {
|
||||
copyTextToClipboard(linkDetail.url).then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("link_copied"),
|
||||
message: t("view_link_copied_to_clipboard"),
|
||||
});
|
||||
});
|
||||
const handleOpenInNewTab = () => window.open(`${viewLink}`, "_blank");
|
||||
|
||||
}, [linkDetail.url, t]);
|
||||
|
||||
const MENU_ITEMS: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
action: () => handleEdit(true),
|
||||
title: t("edit"),
|
||||
icon: Pencil,
|
||||
},
|
||||
{
|
||||
key: "open-new-tab",
|
||||
action: handleOpenInNewTab,
|
||||
title: t("open_in_new_tab"),
|
||||
icon: ExternalLink,
|
||||
},
|
||||
{
|
||||
key: "copy-link",
|
||||
action: handleCopyText,
|
||||
title: t("copy_link"),
|
||||
icon: Link,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: () => linkOperations.remove(linkId),
|
||||
title: t("delete"),
|
||||
icon: Trash2,
|
||||
},
|
||||
];
|
||||
const handleOpenInNewTab = useCallback(() => {
|
||||
window.open(linkDetail.url, "_blank", "noopener,noreferrer");
|
||||
}, [linkDetail.url]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
linkOperations.remove(linkId);
|
||||
}, [linkId, linkOperations]);
|
||||
|
||||
// derived values
|
||||
const menuItems = useMemo<TContextMenuItem[]>(
|
||||
() => [
|
||||
{
|
||||
key: "edit",
|
||||
action: () => handleEdit(true),
|
||||
title: t("edit"),
|
||||
icon: Pencil,
|
||||
},
|
||||
{
|
||||
key: "open-new-tab",
|
||||
action: handleOpenInNewTab,
|
||||
title: t("open_in_new_tab"),
|
||||
icon: ExternalLink,
|
||||
},
|
||||
{
|
||||
key: "copy-link",
|
||||
action: handleCopyText,
|
||||
title: t("copy_link"),
|
||||
icon: Link,
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
action: handleDelete,
|
||||
title: t("delete"),
|
||||
icon: Trash2,
|
||||
},
|
||||
],
|
||||
[handleEdit, handleOpenInNewTab, handleCopyText, handleDelete, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
<LinkItemBlock
|
||||
title={linkDetail.title || linkDetail.url}
|
||||
url={linkDetail.url}
|
||||
createdAt={linkDetail.created_at}
|
||||
menuItems={menuItems}
|
||||
onClick={handleOpenInNewTab}
|
||||
className="cursor-pointer group flex items-center bg-custom-background-100 px-4 w-[230px] h-[56px] border-[0.5px] border-custom-border-200 rounded-md gap-4 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<div className="flex-shrink-0 size-8 rounded p-2 bg-custom-background-80 grid place-items-center">
|
||||
<Icon className="size-4 stroke-2 text-custom-text-350" />
|
||||
</div>
|
||||
<div className="flex-1 truncate">
|
||||
<div className="text-sm font-medium truncate">{linkDetail.title || linkDetail.url}</div>
|
||||
<div className="text-xs font-medium text-custom-text-400">{calculateTimeAgo(linkDetail.created_at)}</div>
|
||||
</div>
|
||||
<div className="hidden group-hover:block">
|
||||
<CustomMenu placement="bottom-end" menuItemsClassName="z-20" closeOnSelect verticalEllipsis>
|
||||
{MENU_ITEMS.map((item) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
item.action();
|
||||
}}
|
||||
className={cn("flex items-center gap-2 w-full ", {
|
||||
"text-custom-text-400": item.disabled,
|
||||
})}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && <item.icon className={cn("h-3 w-3", item.iconClassName)} />}
|
||||
<div>
|
||||
<h5>{item.title}</h5>
|
||||
{item.description && (
|
||||
<p
|
||||
className={cn("text-custom-text-300 whitespace-pre-line", {
|
||||
"text-custom-text-400": item.disabled,
|
||||
})}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
import { FC } from "react";
|
||||
// hooks
|
||||
// ui
|
||||
import { Pencil, Trash2, LinkIcon, ExternalLink } from "lucide-react";
|
||||
import { Pencil, Trash2, ExternalLink } from "lucide-react";
|
||||
import { Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { getIconForLink } from "@plane/utils";
|
||||
// icons
|
||||
// types
|
||||
// helpers
|
||||
|
|
@ -34,6 +35,8 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
|||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
const Icon = getIconForLink(linkDetail.url);
|
||||
|
||||
const toggleIssueLinkModal = (modalToggle: boolean) => {
|
||||
toggleIssueLinkModalStore(modalToggle);
|
||||
setIssueLinkData(linkDetail);
|
||||
|
|
@ -57,7 +60,7 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
|||
>
|
||||
<div className="flex items-start gap-2 truncate">
|
||||
<span className="py-1">
|
||||
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<Icon className="size-3 stroke-2 text-custom-text-350 group-hover:text-custom-text-100 flex-shrink-0" />
|
||||
</span>
|
||||
<Tooltip
|
||||
tooltipContent={linkDetail.title && linkDetail.title !== "" ? linkDetail.title : linkDetail.url}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Pencil, Trash2, LinkIcon, Copy } from "lucide-react";
|
||||
import { Pencil, Trash2, Copy } from "lucide-react";
|
||||
import { EIssueServiceType } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, TOAST_TYPE, setToast, CustomMenu } from "@plane/ui";
|
||||
import { calculateTimeAgo, getIconForLink } from "@plane/utils";
|
||||
// helpers
|
||||
import { calculateTimeAgoShort } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
|
|
@ -37,6 +37,8 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
|||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
const Icon = getIconForLink(linkDetail.url);
|
||||
|
||||
const toggleIssueLinkModal = (modalToggle: boolean) => {
|
||||
toggleIssueLinkModalStore(modalToggle);
|
||||
setIssueLinkData(linkDetail);
|
||||
|
|
@ -48,7 +50,7 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
|||
className="group col-span-12 lg:col-span-6 xl:col-span-4 2xl:col-span-3 3xl:col-span-2 flex items-center justify-between gap-3 h-10 flex-shrink-0 px-3 bg-custom-background-90 hover:bg-custom-background-80 border-[0.5px] border-custom-border-200 rounded"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 truncate flex-grow">
|
||||
<LinkIcon className="size-4 flex-shrink-0 text-custom-text-400 group-hover:text-custom-text-200" />
|
||||
<Icon className="size-4 flex-shrink-0 stroke-2 text-custom-text-350 group-hover:text-custom-text-100" />
|
||||
<Tooltip tooltipContent={linkDetail.url} isMobile={isMobile}>
|
||||
<a
|
||||
href={linkDetail.url}
|
||||
|
|
@ -62,7 +64,7 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
|||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<p className="p-1 text-xs align-bottom leading-5 text-custom-text-400 group-hover-text-custom-text-200">
|
||||
{calculateTimeAgoShort(linkDetail.created_at)}
|
||||
{calculateTimeAgo(linkDetail.created_at)}
|
||||
</p>
|
||||
<span
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { Copy, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
import { Copy, Pencil, Trash2 } from "lucide-react";
|
||||
// plane types
|
||||
import { ILinkDetails } from "@plane/types";
|
||||
// plane ui
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
import { getIconForLink } from "@plane/utils";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
|
|
@ -27,6 +28,8 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
|||
// platform os
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const Icon = getIconForLink(link.url);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
copyTextToClipboard(text).then(() =>
|
||||
setToast({
|
||||
|
|
@ -42,7 +45,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
|||
<div className="flex w-full items-start justify-between gap-2">
|
||||
<div className="flex items-start gap-2 truncate">
|
||||
<span className="py-1">
|
||||
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<Icon className="size-3 stroke-2 text-custom-text-350 group-hover:text-custom-text-100 flex-shrink-0" />
|
||||
</span>
|
||||
<Tooltip tooltipContent={link.title && link.title !== "" ? link.title : link.url} isMobile={isMobile}>
|
||||
<a href={link.url} target="_blank" rel="noopener noreferrer" className="cursor-pointer truncate text-xs">
|
||||
|
|
@ -87,9 +90,8 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-5">
|
||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||
Added {calculateTimeAgo(link.created_at)}
|
||||
<br />
|
||||
<p className="flex items-center gap-1.5 mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||
Added {calculateTimeAgo(link.created_at)}{" "}
|
||||
{createdByDetails && (
|
||||
<>by {createdByDetails?.is_bot ? createdByDetails?.first_name + " Bot" : createdByDetails?.display_name}</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue