This commit is contained in:
parent
b141218adf
commit
91e2448a12
@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hasAnimation?: boolean;
|
||||
disableCursor?: boolean;
|
||||
onClick?: () => void;
|
||||
href?: string;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
hasAnimation = true,
|
||||
disableCursor = false,
|
||||
onClick,
|
||||
href,
|
||||
type = 'button'
|
||||
}) => {
|
||||
const animationClasses = hasAnimation ? `hover:shadow-xl
|
||||
transform
|
||||
hover:scale-[1.01]`
|
||||
: "";
|
||||
const cursorClasses = disableCursor ? "hover:cursor-default"
|
||||
: "hover:cursor-pointer";
|
||||
|
||||
const baseClasses = `bg-gray-800/30
|
||||
px-4 py-2
|
||||
rounded-full
|
||||
backdrop-blur-xs
|
||||
shadow-xl
|
||||
ring-1
|
||||
transition-all
|
||||
duration-200
|
||||
hover:ring-indigo-400/20
|
||||
hover:bg-gray-700/40
|
||||
ring-gray-900
|
||||
text-white
|
||||
transition-colors
|
||||
${animationClasses} ${cursorClasses} ${className}`;
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={baseClasses}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
className={baseClasses}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hasAnimation?: boolean;
|
||||
disableCursor?: boolean;
|
||||
height?: number;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const Card: React.FC<CardProps> = ({ children, className = "", hasAnimation = false, disableCursor = false, height, width }) => {
|
||||
const animationClasses = hasAnimation ? "hover:shadow-xl transform hover:scale-[1.01] transition-all duration-200 hover:ring-indigo-300" : "";
|
||||
const cursorClasses = disableCursor ? "hover:cursor-default" : "";
|
||||
|
||||
const sizeStyles = {
|
||||
...(height && { height: `${height}rem` }),
|
||||
...(width && { width: `${width}rem` })
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-gray-800/30 p-6 rounded-lg backdrop-blur-xs shadow-xl ring-1 ring-gray-700 max-w-md ${animationClasses} ${cursorClasses} ${className}`}
|
||||
style={sizeStyles}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Card;
|
180
src/components/Tooltip.tsx
Normal file
180
src/components/Tooltip.tsx
Normal file
@ -0,0 +1,180 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
interface TooltipProps {
|
||||
content: string;
|
||||
children: React.ReactNode;
|
||||
position?: 'top' | 'bottom' | 'left' | 'right';
|
||||
delay?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Tooltip: React.FC<TooltipProps> = ({
|
||||
content,
|
||||
children,
|
||||
position = 'top',
|
||||
delay = 300,
|
||||
className = ''
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
|
||||
const timeoutRef = useRef<number | null>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const calculateTooltipPosition = () => {
|
||||
if (!triggerRef.current) return { top: 0, left: 0 };
|
||||
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
|
||||
switch (position) {
|
||||
case 'top':
|
||||
top = triggerRect.top - 8; // 8px offset for arrow and spacing
|
||||
left = triggerRect.left + triggerRect.width / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
top = triggerRect.bottom + 8;
|
||||
left = triggerRect.left + triggerRect.width / 2;
|
||||
break;
|
||||
case 'left':
|
||||
top = triggerRect.top + triggerRect.height / 2;
|
||||
left = triggerRect.left - 8;
|
||||
break;
|
||||
case 'right':
|
||||
top = triggerRect.top + triggerRect.height / 2;
|
||||
left = triggerRect.right + 8;
|
||||
break;
|
||||
}
|
||||
|
||||
return { top, left };
|
||||
};
|
||||
|
||||
const showTooltipWithDelay = () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
const position = calculateTooltipPosition();
|
||||
setTooltipPosition(position);
|
||||
setIsVisible(true);
|
||||
// Small delay to allow for positioning before showing
|
||||
setTimeout(() => setShowTooltip(true), 10);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const hideTooltip = () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
setShowTooltip(false);
|
||||
setTimeout(() => setIsVisible(false), 150); // Match transition duration
|
||||
};
|
||||
|
||||
// Update tooltip position on scroll or resize
|
||||
useEffect(() => {
|
||||
if (!isVisible) return;
|
||||
|
||||
const updatePosition = () => {
|
||||
const position = calculateTooltipPosition();
|
||||
setTooltipPosition(position);
|
||||
};
|
||||
|
||||
const handleScroll = () => updatePosition();
|
||||
const handleResize = () => updatePosition();
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
window.addEventListener('resize', handleResize, { passive: true });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [isVisible, position]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getTooltipClasses = () => {
|
||||
const baseClasses = `
|
||||
fixed z-[9999] px-3 py-2 text-sm font-medium text-white bg-gray-900
|
||||
rounded-lg shadow-xl backdrop-blur-sm border border-gray-700
|
||||
transition-all duration-150 ease-in-out pointer-events-none
|
||||
max-w-xs break-words
|
||||
`;
|
||||
|
||||
const transformClasses = {
|
||||
top: '-translate-x-1/2 -translate-y-full',
|
||||
bottom: '-translate-x-1/2',
|
||||
left: '-translate-x-full -translate-y-1/2',
|
||||
right: '-translate-y-1/2'
|
||||
};
|
||||
|
||||
const visibilityClasses = showTooltip
|
||||
? 'opacity-100 scale-100'
|
||||
: 'opacity-0 scale-95';
|
||||
|
||||
return `${baseClasses} ${transformClasses[position]} ${visibilityClasses}`;
|
||||
};
|
||||
|
||||
const getArrowClasses = () => {
|
||||
const baseClasses = 'absolute w-2 h-2 bg-gray-900 border border-gray-700 transform rotate-45';
|
||||
|
||||
const arrowPositions = {
|
||||
top: 'bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 border-t-0 border-l-0',
|
||||
bottom: 'top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 border-b-0 border-r-0',
|
||||
left: 'right-0 top-1/2 transform translate-x-1/2 -translate-y-1/2 border-l-0 border-b-0',
|
||||
right: 'left-0 top-1/2 transform -translate-x-1/2 -translate-y-1/2 border-r-0 border-t-0'
|
||||
};
|
||||
|
||||
return `${baseClasses} ${arrowPositions[position]}`;
|
||||
};
|
||||
|
||||
if (!content) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className={`relative inline-block ${className}`}
|
||||
onMouseEnter={showTooltipWithDelay}
|
||||
onMouseLeave={hideTooltip}
|
||||
onFocus={showTooltipWithDelay}
|
||||
onBlur={hideTooltip}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{isVisible && createPortal(
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className={getTooltipClasses()}
|
||||
style={{
|
||||
top: tooltipPosition.top,
|
||||
left: tooltipPosition.left,
|
||||
}}
|
||||
role="tooltip"
|
||||
aria-hidden={!showTooltip}
|
||||
>
|
||||
{content}
|
||||
<div className={getArrowClasses()} />
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tooltip;
|
Loading…
x
Reference in New Issue
Block a user