import { useSignal } from "@preact/signals"; import { useEffect } from "preact/hooks"; interface Skill { id: string; name: string; level: number; } interface ResumeSkillsProps { skills: Skill[]; } export default function ResumeSkills({ skills }: ResumeSkillsProps) { const animatedLevels = useSignal<{ [key: string]: number }>({}); const hasAnimated = useSignal(false); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !hasAnimated.value) { hasAnimated.value = true; skills.forEach((skill) => { animateSkill(skill.id, skill.level); }); } }); }, { threshold: 0.3 }, ); const skillsElement = document.getElementById("skills-section"); if (skillsElement) { observer.observe(skillsElement); } return () => { if (skillsElement) { observer.unobserve(skillsElement); } }; }, [skills]); const animateSkill = (skillId: string, targetLevel: number) => { const steps = 60; const increment = targetLevel / steps; let currentStep = 0; const animate = () => { if (currentStep <= steps) { const currentValue = Math.min(increment * currentStep, targetLevel); animatedLevels.value = { ...animatedLevels.value, [skillId]: currentValue, }; currentStep++; requestAnimationFrame(animate); } }; requestAnimationFrame(animate); }; return (
{skills.map((skill) => { const currentLevel = animatedLevels.value[skill.id] || 0; const progressValue = currentLevel * 20; return (
{skill.name} {Math.round(currentLevel)}/5
); })}
); }