Files
atridotdad/src/components/ResumeSkills.tsx
Atridad Lahiji d19830a6fa
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
More format changes
2025-07-25 14:38:25 -06:00

96 lines
2.6 KiB
TypeScript

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 (
<div id="skills-section" class="space-y-3 sm:space-y-4">
{skills.map((skill) => {
const currentLevel = animatedLevels.value[skill.id] || 0;
const progressValue = currentLevel * 20;
return (
<div key={skill.id} class="p-1 sm:p-2">
<div class="flex justify-between items-center mb-2">
<span
class="text-sm sm:text-base font-medium truncate pr-2 min-w-0 flex-1"
title={skill.name}
>
{skill.name}
</span>
<span class="text-xs sm:text-sm text-base-content/70 whitespace-nowrap">
{Math.round(currentLevel)}/5
</span>
</div>
<progress
class="progress progress-primary w-full h-2 sm:h-3 min-h-2 transition-all duration-100 ease-out"
value={progressValue}
max="100"
aria-label={`${skill.name} skill level: ${Math.round(currentLevel)} out of 5`}
></progress>
</div>
);
})}
</div>
);
}