"use client";
import { useRef, type FocusEvent, type MouseEvent, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Popover, PopoverAnchor, PopoverContent } from "@kandev/ui/popover";
import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint";
import { useTaskSubtasks, type TaskSubtask } from "@/hooks/domains/kanban/use-task-subtasks";
import { useHoverPopover } from "@/components/integrations/use-hover-popover";
import { cn } from "@/lib/utils";
import { TaskSubtaskRow } from "./task-subtask-row";
const MAX_VISIBLE_SUBTASKS = 12;
const OPEN_DELAY_MS = 200;
const CLOSE_DELAY_MS = 100;
function SubtasksSection({ subtasks }: { subtasks: TaskSubtask[] }) {
const { t } = useTranslation();
if (subtasks.length === 0) return null;
const visible = subtasks.slice(0, MAX_VISIBLE_SUBTASKS);
const overflowCount = subtasks.length - visible.length;
return (
{t("task:subtasksHeading", { count: subtasks.length })}
{visible.map((subtask) => (
))}
{overflowCount > 0 && (
{t("task:moreNotShown", { count: overflowCount })}
)}
);
}
function DesktopTaskTitlePreview({
title,
children,
subtasks,
side,
align,
triggerClassName,
}: {
title: string;
children: ReactNode;
subtasks: TaskSubtask[];
side: "top" | "right" | "bottom" | "left";
align: "start" | "center" | "end";
triggerClassName?: string;
}) {
const triggerRef = useRef(null);
const contentRef = useRef(null);
const keyboardSessionRef = useRef(false);
const hover = useHoverPopover({ openDelayMs: OPEN_DELAY_MS, closeDelayMs: CLOSE_DELAY_MS });
const handleTriggerClick = (event: MouseEvent) => {
if (event.detail !== 0) return;
event.preventDefault();
event.stopPropagation();
keyboardSessionRef.current = true;
hover.onOpenChange(true);
};
const handleContentBlur = (event: FocusEvent) => {
if (!event.currentTarget.contains(event.relatedTarget)) hover.onContentLeave();
};
return (
{
if (!contentRef.current?.contains(document.activeElement)) hover.onContentLeave();
}}
onFocusCapture={hover.onContentEnter}
onBlurCapture={handleContentBlur}
onOpenAutoFocus={(event) => {
event.preventDefault();
if (keyboardSessionRef.current) {
contentRef.current
?.querySelector(
'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])',
)
?.focus();
}
}}
onCloseAutoFocus={(event) => {
event.preventDefault();
if (keyboardSessionRef.current) triggerRef.current?.focus();
keyboardSessionRef.current = false;
}}
onClick={(event) => event.stopPropagation()}
onPointerDown={(event) => event.stopPropagation()}
>
{title}
);
}
/** Interactive desktop preview with direct mobile navigation as the coarse-pointer fallback. */
export function TaskTitleHoverCard({
taskId,
title,
children,
side = "bottom",
align = "start",
triggerClassName,
}: {
taskId: string;
title: string;
children: ReactNode;
side?: "top" | "right" | "bottom" | "left";
align?: "start" | "center" | "end";
triggerClassName?: string;
}) {
const { isFinePointer } = useResponsiveBreakpoint();
const subtasks = useTaskSubtasks(taskId);
if (!isFinePointer || subtasks.length === 0) return <>{children}>;
return (
{children}
);
}