"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} ); }