import { useEffect, useRef, useState, type ReactNode } from "react"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; import { proietta } from "@/lib/molla"; export type VoceSottosezione = { id: string; label: string; contenuto: ReactNode; }; /** Tween breve: evita molle + exit che tengono due pannelli in DOM insieme. */ const transizioneTab = { type: "tween" as const, duration: 0.16, ease: [0.25, 0.1, 0.25, 1] }; /** * Barra di sottosezioni in un'unica fila scorrevole (swipe orizzontale) e * pannello che mostra una sola sezione alla volta, cambiabile anche con * swipe sul contenuto. * * Il pannello precedente si smonta subito (niente AnimatePresence/exit): * al cambio tab resta un solo albero da animare in ingresso. */ export function BarraSottosezioni({ voci, defaultId, }: { voci: VoceSottosezione[]; defaultId?: string; }) { const [attiva, setAttiva] = useState(defaultId ?? voci[0]?.id ?? ""); const direzione = useRef(0); const tabRefs = useRef>({}); const ridotto = useReducedMotion(); const indice = Math.max( 0, voci.findIndex((v) => v.id === attiva), ); const voce = voci[indice] ?? voci[0]; useEffect(() => { // `auto`: lo smooth competerebbe col tween del pannello sul main thread. tabRefs.current[attiva]?.scrollIntoView({ behavior: "auto", inline: "center", block: "nearest", }); }, [attiva]); function vaiA(nuovo: number) { if (nuovo < 0 || nuovo >= voci.length) return; const target = voci[nuovo]; if (!target || target.id === attiva) return; direzione.current = nuovo > indice ? 1 : -1; setAttiva(target.id); } if (!voce) return null; return (
{voci.map((v) => { const selezionata = v.id === attiva; return ( ); })}
{ const arrivo = info.offset.x + proietta(info.velocity.x); if (arrivo < -60) vaiA(indice + 1); else if (arrivo > 60) vaiA(indice - 1); }} initial={ridotto ? { opacity: 0 } : { opacity: 0, x: direzione.current * 20 }} animate={{ opacity: 1, x: 0 }} transition={ridotto ? { duration: 0.1 } : transizioneTab} className="touch-pan-y px-5 py-4" >

{voce.label}

{voce.contenuto}
); }