consertando problema com as pastas
All checks were successful
Frontend Build and Deploy / build (push) Successful in 22s

This commit is contained in:
José Henrique 2025-06-10 13:24:52 -03:00
parent b141218adf
commit add1db4376
9 changed files with 217 additions and 6 deletions

View File

@ -7,7 +7,7 @@ import AssetsComponent from './AssetsComponent';
import BasicCandidateInfoComponent from './BasicCandidateInfoComponent'; import BasicCandidateInfoComponent from './BasicCandidateInfoComponent';
import SocialMediaComponent from './SocialMediaComponent'; import SocialMediaComponent from './SocialMediaComponent';
import IncomeExpenseComponent from './IncomeExpenseComponent'; import IncomeExpenseComponent from './IncomeExpenseComponent';
import Button from '../../Components/Button'; import Button from '../../shared/Button';
const CandidatePage: React.FC = () => { const CandidatePage: React.FC = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { DocumentTextIcon } from '@heroicons/react/24/outline'; import { DocumentTextIcon } from '@heroicons/react/24/outline';
import { type Election } from '../../api'; import { type Election } from '../../api';
import Tooltip from '../../Components/Tooltip'; import Tooltip from '../../shared/Tooltip';
interface ElectionsComponentProps { interface ElectionsComponentProps {
elections: Election[] | null; elections: Election[] | null;

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { openCandApi } from '../api'; import { openCandApi } from '../api';
import type { OpenCandDataAvailabilityStats } from '../api/apiModels'; import type { OpenCandDataAvailabilityStats } from '../api/apiModels';
import Card from '../Components/Card'; import Card from '../shared/Card';
const DataStatsPage: React.FC = () => { const DataStatsPage: React.FC = () => {
const [stats, setStats] = useState<OpenCandDataAvailabilityStats | null>(null); const [stats, setStats] = useState<OpenCandDataAvailabilityStats | null>(null);

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ArrowDownOnSquareStackIcon, BookOpenIcon, ChartBarIcon, DocumentTextIcon, LightBulbIcon } from '@heroicons/react/24/outline'; import { ArrowDownOnSquareStackIcon, BookOpenIcon, ChartBarIcon, DocumentTextIcon, LightBulbIcon } from '@heroicons/react/24/outline';
import Card from '../Components/Card'; import Card from '../shared/Card';
const FeatureCard: React.FC<{ icon: React.ElementType, title: string, children: React.ReactNode }> = ({ icon: Icon, title, children }) => { const FeatureCard: React.FC<{ icon: React.ElementType, title: string, children: React.ReactNode }> = ({ icon: Icon, title, children }) => {
return ( return (

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import Button from '../Components/Button'; import Button from '../shared/Button';
import NavbarMatrixBackground from './NavbarMatrixBackground'; import NavbarMatrixBackground from './NavbarMatrixBackground';
const Navbar: React.FC = () => { const Navbar: React.FC = () => {

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { openCandApi, type PlatformStats, ApiError } from '../api'; import { openCandApi, type PlatformStats, ApiError } from '../api';
import Card from '../Components/Card'; import Card from '../shared/Card';
interface StatCardProps { interface StatCardProps {
title: string; title: string;

31
src/shared/Card.tsx Normal file
View File

@ -0,0 +1,31 @@
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/shared/Tooltip.tsx Normal file
View 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;