Files
pangolin/src/components/resource-launcher/useLauncherResourceAction.ts
2026-07-01 14:30:43 -04:00

128 lines
3.3 KiB
TypeScript

"use client";
import { useToast } from "@app/hooks/useToast";
import { isSafeUrlForLink } from "@app/lib/launcherResourceAccess";
import { useTranslations } from "next-intl";
import { useCallback, type KeyboardEvent, type MouseEvent } from "react";
type LauncherResourceActionInput = {
accessUrl?: string | null;
accessCopyValue: string;
};
export function useLauncherResourceAction({
accessUrl,
accessCopyValue
}: LauncherResourceActionInput) {
const { toast } = useToast();
const t = useTranslations();
const href = accessUrl ?? undefined;
const canLink = Boolean(href && isSafeUrlForLink(href));
const isClickable = canLink || Boolean(accessCopyValue);
const handleAction = useCallback(() => {
if (canLink && href) {
window.open(href, "_blank", "noopener,noreferrer");
return;
}
if (!accessCopyValue) {
return;
}
void navigator.clipboard.writeText(accessCopyValue).then(() => {
toast({
title: t("resourceLauncherCopiedToClipboard"),
description: t("resourceLauncherCopiedAccessDescription"),
duration: 2000
});
});
}, [accessCopyValue, canLink, href, t, toast]);
return { handleAction, isClickable };
}
export function isLauncherResourceInteractiveTarget(
target: EventTarget | null,
container?: EventTarget | null
): boolean {
if (!(target instanceof Element)) {
return false;
}
const interactive = target.closest(
"a, button, [role='button'], input, textarea, select"
);
if (!interactive) {
return false;
}
if (container instanceof Element && interactive === container) {
return false;
}
return true;
}
function handleLauncherResourceClick(
event: MouseEvent,
handleAction: () => void
) {
if (
isLauncherResourceInteractiveTarget(event.target, event.currentTarget)
) {
return;
}
handleAction();
}
export function getLauncherResourceSelectProps(onSelect: () => void) {
return {
onClick: (event: MouseEvent) => {
if (
isLauncherResourceInteractiveTarget(
event.target,
event.currentTarget
)
) {
return;
}
onSelect();
},
className: "cursor-pointer",
role: "button" as const,
tabIndex: 0,
onKeyDown: (event: KeyboardEvent) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onSelect();
}
}
};
}
export function getLauncherResourceClickProps(
handleAction: () => void,
isClickable: boolean
) {
return {
onClick: (event: MouseEvent) =>
handleLauncherResourceClick(event, handleAction),
className: isClickable ? "cursor-pointer" : undefined,
role: isClickable ? ("button" as const) : undefined,
tabIndex: isClickable ? 0 : undefined,
onKeyDown: isClickable
? (event: KeyboardEvent) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
handleAction();
}
}
: undefined
};
}