100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
|
|
import type { Categoria } from '../../../types'
|
||
|
|
|
||
|
|
interface RuletaSVGProps {
|
||
|
|
categorias: Categoria[]
|
||
|
|
rotacion: number
|
||
|
|
isSpinning: boolean
|
||
|
|
onClick: () => void
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function RuletaSVG({ categorias, rotacion, isSpinning, onClick }: RuletaSVGProps) {
|
||
|
|
const cx = 200
|
||
|
|
const cy = 200
|
||
|
|
const r = 180
|
||
|
|
const total = categorias.length
|
||
|
|
const angleStep = 360 / total
|
||
|
|
|
||
|
|
function polarToCartesian(angle: number, radius: number) {
|
||
|
|
const rad = (angle - 90) * (Math.PI / 180)
|
||
|
|
return {
|
||
|
|
x: cx + radius * Math.cos(rad),
|
||
|
|
y: cy + radius * Math.sin(rad),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function sectorPath(startAngle: number, endAngle: number) {
|
||
|
|
const start = polarToCartesian(startAngle, r)
|
||
|
|
const end = polarToCartesian(endAngle, r)
|
||
|
|
const largeArc = endAngle - startAngle > 180 ? 1 : 0
|
||
|
|
return `M ${cx} ${cy} L ${start.x} ${start.y} A ${r} ${r} 0 ${largeArc} 1 ${end.x} ${end.y} Z`
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="relative flex items-center justify-center">
|
||
|
|
|
||
|
|
{/* Indicador */}
|
||
|
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-2 z-10">
|
||
|
|
<div className="w-0 h-0 border-l-[12px] border-r-[12px] border-t-[24px] border-l-transparent border-r-transparent border-t-gray-800 drop-shadow-md" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Ruleta SVG */}
|
||
|
|
<svg
|
||
|
|
width="400"
|
||
|
|
height="400"
|
||
|
|
viewBox="0 0 400 400"
|
||
|
|
onClick={!isSpinning ? onClick : undefined}
|
||
|
|
className={`select-none drop-shadow-xl ${!isSpinning ? 'cursor-pointer' : 'cursor-not-allowed'}`}
|
||
|
|
style={{
|
||
|
|
transition: 'transform 4s cubic-bezier(0.25, 0.1, 0.25, 1)',
|
||
|
|
transform: `rotate(${rotacion}deg)`,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{categorias.map((cat, i) => {
|
||
|
|
const startAngle = i * angleStep
|
||
|
|
const endAngle = startAngle + angleStep
|
||
|
|
const midAngle = startAngle + angleStep / 2
|
||
|
|
const textPos = polarToCartesian(midAngle, r * 0.62)
|
||
|
|
|
||
|
|
return (
|
||
|
|
<g key={cat.id}>
|
||
|
|
{/* Sector */}
|
||
|
|
<path
|
||
|
|
d={sectorPath(startAngle, endAngle)}
|
||
|
|
fill={cat.color}
|
||
|
|
stroke="white"
|
||
|
|
strokeWidth="3"
|
||
|
|
/>
|
||
|
|
{/* Emoji */}
|
||
|
|
<text
|
||
|
|
x={textPos.x}
|
||
|
|
y={textPos.y - 14}
|
||
|
|
textAnchor="middle"
|
||
|
|
dominantBaseline="middle"
|
||
|
|
fontSize="28"
|
||
|
|
>
|
||
|
|
{cat.emoji}
|
||
|
|
</text>
|
||
|
|
{/* Nombre */}
|
||
|
|
<text
|
||
|
|
x={textPos.x}
|
||
|
|
y={textPos.y + 16}
|
||
|
|
textAnchor="middle"
|
||
|
|
dominantBaseline="middle"
|
||
|
|
fontSize="13"
|
||
|
|
fontWeight="bold"
|
||
|
|
fill="white"
|
||
|
|
>
|
||
|
|
{cat.nombre}
|
||
|
|
</text>
|
||
|
|
</g>
|
||
|
|
)
|
||
|
|
})}
|
||
|
|
|
||
|
|
{/* Centro */}
|
||
|
|
<circle cx={cx} cy={cy} r={22} fill="white" stroke="#e5e7eb" strokeWidth="3" />
|
||
|
|
<circle cx={cx} cy={cy} r={10} fill="#7C3AED" />
|
||
|
|
</svg>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|