Files
Portfolio/src/components/sections/Skills.tsx
T
2026-01-25 19:26:21 +01:00

163 lines
5.6 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { Database, Server, Code2, Bot, Workflow, FileCode2 } from 'lucide-react';
interface Skill {
name: string;
level: number;
}
interface SkeletonProps {
className?: string;
}
const Skeleton: React.FC<SkeletonProps> = ({ className }) => (
<div className={`animate-pulse bg-zinc-700/50 rounded ${className}`}></div>
);
const SkillsSkeleton = () => (
<section id="skills" className="py-24 relative" aria-label="Loading skills" aria-busy="true">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<Skeleton className="h-8 w-48 mb-12" />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Skills Progress Bars Skeleton */}
<div className="space-y-6">
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
<div key={i} className="space-y-2">
<div className="flex justify-between items-center">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-12" />
</div>
<Skeleton className="h-2 w-full rounded-full" />
</div>
))}
</div>
{/* Skills Icons Grid Skeleton */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="p-6 rounded-xl bg-zinc-800/30 backdrop-blur-sm">
<Skeleton className="w-8 h-8 mx-auto mb-3" />
<Skeleton className="h-4 w-16 mx-auto" />
</div>
))}
</div>
</div>
</div>
</section>
);
const iconMap: Record<string, React.ReactNode> = {
'AI & LLMs': <Bot className="w-8 h-8" />,
'Automation': <Workflow className="w-8 h-8" />,
'Automatizacija': <Workflow className="w-8 h-8" />,
'Python': <Code2 className="w-8 h-8" />,
'TypeScript': <FileCode2 className="w-8 h-8" />,
'Datenbank': <Database className="w-8 h-8" />,
'Database': <Database className="w-8 h-8" />,
'Baze podataka': <Database className="w-8 h-8" />,
'DevOps': <Server className="w-8 h-8" />
};
const Skills = () => {
const t = useTranslations('pages.home.skills');
const [skills, setSkills] = useState<Skill[]>([]);
useEffect(() => {
// Temporary delay to see skeleton loading state
const timer = setTimeout(() => {
try {
const translatedSkills = t.raw('skills') as Skill[];
if (Array.isArray(translatedSkills)) {
setSkills(translatedSkills);
}
} catch (error) {
console.error('Error loading skills:', error);
setSkills([]);
}
}, 2000);
return () => clearTimeout(timer);
}, [t]);
if (skills.length === 0) {
return <SkillsSkeleton />;
}
return (
<section id="skills" className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<h2 className="text-3xl font-bold text-white mb-12">
{t('title')}
</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Skills Progress Bars */}
<div className="space-y-6">
{skills.map((skill, idx) => (
<motion.div
key={skill.name}
className="space-y-2"
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: idx * 0.1 }}
>
<div className="flex justify-between items-center">
<span className="text-white text-sm font-medium">{skill.name}</span>
<span className="text-white text-sm">{skill.level}%</span>
</div>
<div
className="h-2 bg-zinc-800/50 rounded-full overflow-hidden"
role="progressbar"
aria-valuenow={skill.level}
aria-valuemin={0}
aria-valuemax={100}
>
<motion.div
className="h-full rounded-full bg-gradient-to-r from-zinc-600 to-zinc-500"
initial={{ width: 0 }}
whileInView={{ width: `${skill.level}%` }}
viewport={{ once: true }}
transition={{ duration: 1, delay: idx * 0.1 }}
/>
</div>
</motion.div>
))}
</div>
{/* Skills Icons Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{skills.map((skill, idx) => (
<motion.div
key={skill.name}
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: idx * 0.1 }}
className="p-6 rounded-xl bg-zinc-800/30 backdrop-blur-sm flex flex-col items-center justify-center gap-3"
>
<div className="text-white">
{iconMap[skill.name] || <Code2 className="w-8 h-8" />}
</div>
<span className="text-white text-sm text-center">{skill.name}</span>
</motion.div>
))}
</div>
</div>
</motion.div>
</div>
</section>
);
};
export default Skills;