feat: enhance TaskCard with tooltip functionality

- Added tooltip to TaskCard title for improved user interaction, displaying the title on hover.
- Introduced cleanup for timeout on component unmount to prevent memory leaks.
- Refactored title rendering to use a new TitleWithTooltip component for better code organization.
This commit is contained in:
Julien Froidefond
2025-09-17 11:49:01 +02:00
parent 60a9d0807e
commit b3052f34f2

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { Task } from '@/lib/types';
import { formatDistanceToNow } from 'date-fns';
import { fr } from 'date-fns/locale';
@@ -20,6 +20,8 @@ interface TaskCardProps {
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView = false }: TaskCardProps) {
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [editTitle, setEditTitle] = useState(task.title);
const [showTooltip, setShowTooltip] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const { tags: availableTags } = useTasksContext();
// Configuration du draggable
@@ -37,6 +39,16 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
useEffect(() => {
setEditTitle(task.title);
}, [task.title]);
// Nettoyer le timeout au démontage
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const handleDelete = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -83,6 +95,23 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
handleTitleCancel();
}
};
const handleMouseEnter = () => {
if (!isEditingTitle) {
timeoutRef.current = setTimeout(() => {
setShowTooltip(true);
}, 100);
}
};
const handleMouseLeave = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setShowTooltip(false);
};
// Style de transformation pour le drag
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
@@ -92,6 +121,29 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
const emojiRegex = /(?:[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}])(?:[\u{200D}][\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{FE0F}])*/gu;
const titleEmojis = task.title.match(emojiRegex) || [];
const titleWithoutEmojis = task.title.replace(emojiRegex, '').trim();
// Composant titre avec tooltip
const TitleWithTooltip = () => (
<div className="relative flex-1">
<h4
className="font-mono text-sm font-medium text-[var(--foreground)] leading-tight line-clamp-2 cursor-pointer hover:text-[var(--primary)] transition-colors"
onClick={handleTitleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
>
{titleWithoutEmojis}
</h4>
{/* Tooltip */}
{showTooltip && (
<div className="absolute z-50 bottom-full left-0 mb-2 px-2 py-1 bg-[var(--background)] border border-[var(--border)] rounded-md shadow-lg max-w-xs whitespace-normal break-words text-xs font-mono text-[var(--foreground)]">
{titleWithoutEmojis}
<div className="absolute top-full left-2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-[var(--border)]"></div>
</div>
)}
</div>
);
// Si pas d'emoji dans le titre, utiliser l'emoji du premier tag
let displayEmojis: string[] = titleEmojis;
@@ -149,13 +201,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
/>
) : (
<h4
className="font-mono text-sm font-medium text-[var(--foreground)] leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-[var(--primary)] transition-colors"
onClick={handleTitleClick}
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
>
{titleWithoutEmojis}
</h4>
<TitleWithTooltip />
)}
<div className="flex items-center gap-1 flex-shrink-0">
@@ -234,13 +280,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
/>
) : (
<h4
className="font-mono text-sm font-medium text-[var(--foreground)] leading-tight line-clamp-2 flex-1 cursor-pointer hover:text-[var(--primary)] transition-colors"
onClick={handleTitleClick}
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
>
{titleWithoutEmojis}
</h4>
<TitleWithTooltip />
)}
<div className="flex items-center gap-1 flex-shrink-0">