import { useId, useState, type ComponentPropsWithoutRef, type ReactNode } from "react"; import { Link } from "@tanstack/react-router"; import { ChevronDown } from "lucide-react"; import { cn } from "@/lib/utils"; import { inizialiDa, statoMeta, type Stato } from "@/lib/crapp-data"; import { nomeCompleto } from "@/lib/giocatori-squadra"; import { useGiocatoreBase } from "@/lib/user-store"; import { Avatar } from "@/components/crapp/Avatar"; import { Reveal } from "@/components/motion/Reveal"; import { Numero } from "@/components/motion/Numero"; export function TeamLogo({ className, /** * Di default l'icona della PWA, che ha dentro il nome dell'app. Dove serve lo * stemma della squadra e basta si passa `/logo-nerorosso.svg`. */ src = "/icon-192.png", }: { className?: string; src?: string; }) { return ( CRAP Volley ); } /** * Superficie standard dell'app: era ripetuta a mano una ventina di volte come * `rounded-3xl bg-card p-4 shadow-card`, quindi cambiare raggio od ombra * voleva dire toccare venti file. * * Gerarchia dei raggi: contenitore `3xl` → elemento interno `2xl` → * controllo `full`. */ export function Card({ className, as: Tag = "div", ...props }: ComponentPropsWithoutRef<"div"> & { as?: "div" | "article" | "section" }) { return ; } /** * Accesso al profilo in alto a destra: la BottomNav ha quattro voci e questa è * l'unica porta verso `/profilo`. Sulla pagina del profilo si passa `azione` a * `PageHeader` con il logo che porta alla home. * * Usa `useGiocatoreBase` (sola anagrafica) e non `useIo`: qui serve solo id e * iniziali, mentre `useIo` calcola l'intera rosa con statistiche (MVP, pagelle, * cacche, palloni, infortuni). Essendo in un componente montato su quasi ogni * pagina, quei moduli finirebbero nel bundle condiviso di tutte le rotte. */ export function LinkProfilo() { const g = useGiocatoreBase(); if (!g) return ; return ( ); } export function PageHeader({ titolo, sottotitolo, azione, }: { titolo: string; sottotitolo?: string; /** Sostituisce il link al profilo in alto a destra. */ azione?: ReactNode; }) { return (

{titolo}

{sottotitolo ? (

{sottotitolo}

) : null}
{azione ?? }
); } export function Section({ titolo, azione, children, indice = 0, }: { titolo?: string; azione?: ReactNode; children: ReactNode; indice?: number; }) { return ( {titolo || azione ? (
{titolo ?

{titolo}

: } {azione}
) : null} {children}
); } /** Sezione con titolo cliccabile: il contenuto si apre e chiude. `anteprima` resta sempre visibile. */ export function SezioneTendina({ titolo, children, anteprima, azione, defaultAperta = false, indice = 0, }: { titolo: string; children: ReactNode; anteprima?: ReactNode; azione?: ReactNode; defaultAperta?: boolean; indice?: number; }) { const [aperta, setAperta] = useState(defaultAperta); const id = useId(); return ( {anteprima}
{aperta ? children : null}
); } /** * Classi condivise di input, select e textarea nei form dell'app. `h-10`: * `input[type="date"]` ha al suo interno segmenti e icona nativi che, anche * con `appearance-none`, restano un filo più alti di un input di testo a * parità di padding — un'altezza esplicita allinea tutti i campi tra loro. */ export const classiInput = "h-10 w-full min-w-0 rounded-xl border border-border bg-background px-3 py-2 text-sm"; /** * Etichetta + controllo di un form. `min-w-0`: dentro `grid-cols-2` questa label * è l'elemento di griglia e ha `min-width: auto`, quindi si allarga fino al * contenuto invece di stare nella colonna. Con un controllo nativo largo dentro * (una data o un'ora su iOS) la coppia sfonda la card. */ export function Campo({ label, children }: { label: string; children: ReactNode }) { return ( ); } /** * `` ignora parzialmente il padding di * `classiInput` (soprattutto su Android), risultando più alto o più basso * degli input accanto: `appearance-none` lo riporta a una scatola CSS * normale, la freccia va poi ridisegnata a mano perché sparisce con lui. */ export function Select(props: ComponentPropsWithoutRef<"select">) { return (