consertando problema com as pastas
All checks were successful
Frontend Build and Deploy / build (push) Successful in 22s
All checks were successful
Frontend Build and Deploy / build (push) Successful in 22s
This commit is contained in:
parent
b141218adf
commit
add1db4376
@ -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 }>();
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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 (
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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
31
src/shared/Card.tsx
Normal 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
180
src/shared/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