Initial commit

This commit is contained in:
2026-01-11 22:04:05 +01:00
commit 87a8b6b844
549 changed files with 96211 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
"use client";
import React, { useState } from "react";
import { Info } from "lucide-react";
import { cn } from "@/lib/utils";
interface HelpTooltipProps {
/**
* Contenu du tooltip
*/
content: string | React.ReactNode;
/**
* Position du tooltip
* @default "top"
*/
position?: "top" | "bottom" | "left" | "right";
/**
* Largeur maximale du tooltip
* @default "250px"
*/
maxWidth?: string;
/**
* Classe CSS personnalisée pour le wrapper
*/
className?: string;
/**
* Si vrai, affiche une icône d'information
* @default true
*/
showIcon?: boolean;
/**
* Contenu personnalisé pour déclencher le tooltip
*/
trigger?: React.ReactNode;
}
export function HelpTooltip({
content,
position = "top",
maxWidth = "250px",
className,
showIcon = true,
trigger,
}: HelpTooltipProps) {
const [isVisible, setIsVisible] = useState(false);
const positionClasses = {
top: "bottom-full left-1/2 -translate-x-1/2 mb-2 pb-1",
bottom: "top-full left-1/2 -translate-x-1/2 mt-2 pt-1",
left: "right-full top-1/2 -translate-y-1/2 mr-2 pr-1",
right: "left-full top-1/2 -translate-y-1/2 ml-2 pl-1",
};
const arrowClasses = {
top: "top-full left-1/2 -translate-x-1/2 border-t-slate-800 border-r-transparent border-b-transparent border-l-transparent",
bottom: "bottom-full left-1/2 -translate-x-1/2 border-b-slate-800 border-r-transparent border-t-transparent border-l-transparent",
left: "left-full top-1/2 -translate-y-1/2 border-l-slate-800 border-t-transparent border-r-transparent border-b-transparent",
right: "right-full top-1/2 -translate-y-1/2 border-r-slate-800 border-t-transparent border-l-transparent border-b-transparent",
};
const defaultTrigger = (
<button
type="button"
className={cn(
"inline-flex items-center justify-center w-4 h-4 rounded-full",
"text-slate-400 hover:text-indigo-600 hover:bg-indigo-50",
"transition-colors duration-150",
"focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1",
className
)}
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
onFocus={() => setIsVisible(true)}
onBlur={() => setIsVisible(false)}
aria-label="Information"
>
<Info className="w-3 h-3" strokeWidth={2.5} />
</button>
);
return (
<div className="relative inline-flex">
<div
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
>
{trigger || defaultTrigger}
</div>
{isVisible && (
<div
className={cn(
"absolute z-50 pointer-events-none",
positionClasses[position]
)}
style={{ maxWidth }}
role="tooltip"
aria-hidden={!isVisible}
>
{/* Arrow */}
<div
className={cn(
"absolute w-0 h-0 border-4",
arrowClasses[position]
)}
/>
{/* Content */}
<div className="relative bg-slate-800 text-slate-100 text-xs rounded-md shadow-lg px-3 py-2 leading-relaxed">
{typeof content === "string" ? (
<p>{content}</p>
) : (
<div>{content}</div>
)}
</div>
</div>
)}
</div>
);
}