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:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Task } from '@/lib/types';
|
import { Task } from '@/lib/types';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { fr } from 'date-fns/locale';
|
import { fr } from 'date-fns/locale';
|
||||||
@@ -20,6 +20,8 @@ interface TaskCardProps {
|
|||||||
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView = false }: TaskCardProps) {
|
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView = false }: TaskCardProps) {
|
||||||
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||||
const [editTitle, setEditTitle] = useState(task.title);
|
const [editTitle, setEditTitle] = useState(task.title);
|
||||||
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const { tags: availableTags } = useTasksContext();
|
const { tags: availableTags } = useTasksContext();
|
||||||
|
|
||||||
// Configuration du draggable
|
// Configuration du draggable
|
||||||
@@ -37,6 +39,16 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditTitle(task.title);
|
setEditTitle(task.title);
|
||||||
}, [task.title]);
|
}, [task.title]);
|
||||||
|
|
||||||
|
// Nettoyer le timeout au démontage
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleDelete = async (e: React.MouseEvent) => {
|
const handleDelete = async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -83,6 +95,23 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
|||||||
handleTitleCancel();
|
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
|
// Style de transformation pour le drag
|
||||||
const style = transform ? {
|
const style = transform ? {
|
||||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
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 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 titleEmojis = task.title.match(emojiRegex) || [];
|
||||||
const titleWithoutEmojis = task.title.replace(emojiRegex, '').trim();
|
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
|
// Si pas d'emoji dans le titre, utiliser l'emoji du premier tag
|
||||||
let displayEmojis: string[] = titleEmojis;
|
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"
|
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h4
|
<TitleWithTooltip />
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-1 flex-shrink-0">
|
<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"
|
className="flex-1 bg-transparent border-none outline-none text-[var(--foreground)] font-mono text-sm font-medium leading-tight"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h4
|
<TitleWithTooltip />
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-1 flex-shrink-0">
|
<div className="flex items-center gap-1 flex-shrink-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user