Compatta la classifica interna Squadra e allinea le tab a sottolineatura.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,8 @@ export type VoceSottosezione = {
|
|||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
contenuto: ReactNode;
|
contenuto: ReactNode;
|
||||||
|
/** Se true, non ripete il titolo della tab sopra il contenuto (es. Classifica già nella barra). */
|
||||||
|
nascondiTitolo?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Tween breve: evita molle + exit che tengono due pannelli in DOM insieme. */
|
/** Tween breve: evita molle + exit che tengono due pannelli in DOM insieme. */
|
||||||
@@ -17,15 +19,18 @@ const transizioneTab = { type: "tween" as const, duration: 0.16, ease: [0.25, 0.
|
|||||||
* pannello che mostra una sola sezione alla volta, cambiabile anche con
|
* pannello che mostra una sola sezione alla volta, cambiabile anche con
|
||||||
* swipe sul contenuto.
|
* swipe sul contenuto.
|
||||||
*
|
*
|
||||||
* Il pannello precedente si smonta subito (niente AnimatePresence/exit):
|
* `variante="sottolineatura"`: tab a testo con sottolineatura rossa e senza titolo
|
||||||
* al cambio tab resta un solo albero da animare in ingresso.
|
* ripetuto sotto la barra (Squadra e Classifica CSI). Default `pillole`: restano le
|
||||||
|
* pill e i titoli usati dal Profilo.
|
||||||
*/
|
*/
|
||||||
export function BarraSottosezioni({
|
export function BarraSottosezioni({
|
||||||
voci,
|
voci,
|
||||||
defaultId,
|
defaultId,
|
||||||
|
variante = "pillole",
|
||||||
}: {
|
}: {
|
||||||
voci: VoceSottosezione[];
|
voci: VoceSottosezione[];
|
||||||
defaultId?: string;
|
defaultId?: string;
|
||||||
|
variante?: "pillole" | "sottolineatura";
|
||||||
}) {
|
}) {
|
||||||
const [attiva, setAttiva] = useState(defaultId ?? voci[0]?.id ?? "");
|
const [attiva, setAttiva] = useState(defaultId ?? voci[0]?.id ?? "");
|
||||||
const direzione = useRef(0);
|
const direzione = useRef(0);
|
||||||
@@ -36,6 +41,7 @@ export function BarraSottosezioni({
|
|||||||
voci.findIndex((v) => v.id === attiva),
|
voci.findIndex((v) => v.id === attiva),
|
||||||
);
|
);
|
||||||
const voce = voci[indice] ?? voci[0];
|
const voce = voci[indice] ?? voci[0];
|
||||||
|
const sottolineatura = variante === "sottolineatura";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// `auto`: lo smooth competerebbe col tween del pannello sul main thread.
|
// `auto`: lo smooth competerebbe col tween del pannello sul main thread.
|
||||||
@@ -58,11 +64,19 @@ export function BarraSottosezioni({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="border-b border-border bg-background/80 pt-3 backdrop-blur-md">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"border-b border-border bg-background",
|
||||||
|
!sottolineatura && "bg-background/80 pt-3 backdrop-blur-md",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-label="Sottosezioni"
|
aria-label="Sottosezioni"
|
||||||
className="-mx-0 flex snap-x snap-mandatory gap-1.5 overflow-x-auto px-5 pb-3 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
className={cn(
|
||||||
|
"flex snap-x snap-mandatory overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
||||||
|
sottolineatura ? "gap-0 px-2" : "-mx-0 gap-1.5 px-5 pb-3",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{voci.map((v) => {
|
{voci.map((v) => {
|
||||||
const selezionata = v.id === attiva;
|
const selezionata = v.id === attiva;
|
||||||
@@ -80,13 +94,21 @@ export function BarraSottosezioni({
|
|||||||
vaiA(i);
|
vaiA(i);
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
// grow+basis-0: se le voci ci stanno riempiono la riga in parti uguali,
|
"snap-center shrink-0 grow basis-0 touch-manipulation whitespace-nowrap text-xs font-bold uppercase tracking-wide transition-colors",
|
||||||
// altrimenti shrink-0 le tiene leggibili e la barra torna a scorrere.
|
"min-h-11",
|
||||||
"snap-center shrink-0 grow basis-0 rounded-full px-3.5 py-2 text-xs font-bold uppercase tracking-wide transition-colors",
|
sottolineatura
|
||||||
"min-h-11 touch-manipulation whitespace-nowrap",
|
? cn(
|
||||||
selezionata
|
"rounded-none border-b-2 px-3 py-3",
|
||||||
? "bg-accent text-accent-foreground shadow-pop"
|
selezionata
|
||||||
: "bg-secondary text-muted-foreground",
|
? "border-accent text-foreground"
|
||||||
|
: "border-transparent text-muted-foreground",
|
||||||
|
)
|
||||||
|
: cn(
|
||||||
|
"rounded-full px-3.5 py-2",
|
||||||
|
selezionata
|
||||||
|
? "bg-accent text-accent-foreground shadow-pop"
|
||||||
|
: "bg-secondary text-muted-foreground",
|
||||||
|
),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{v.label}
|
{v.label}
|
||||||
@@ -113,9 +135,11 @@ export function BarraSottosezioni({
|
|||||||
initial={ridotto ? { opacity: 0 } : { opacity: 0, x: direzione.current * 20 }}
|
initial={ridotto ? { opacity: 0 } : { opacity: 0, x: direzione.current * 20 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={ridotto ? { duration: 0.1 } : transizioneTab}
|
transition={ridotto ? { duration: 0.1 } : transizioneTab}
|
||||||
className="touch-pan-y px-5 py-4"
|
className={cn("touch-pan-y", voce.nascondiTitolo ? "px-0 pt-0 pb-4" : "px-5 py-4")}
|
||||||
>
|
>
|
||||||
<h2 className="mb-3 font-display-sm text-lg uppercase">{voce.label}</h2>
|
{voce.nascondiTitolo || sottolineatura ? null : (
|
||||||
|
<h2 className="mb-3 font-display-sm text-lg uppercase">{voce.label}</h2>
|
||||||
|
)}
|
||||||
{voce.contenuto}
|
{voce.contenuto}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ function Classifica() {
|
|||||||
|
|
||||||
<BarraSottosezioni
|
<BarraSottosezioni
|
||||||
defaultId={tab ?? "classifica"}
|
defaultId={tab ?? "classifica"}
|
||||||
|
variante="sottolineatura"
|
||||||
voci={[
|
voci={[
|
||||||
{
|
{
|
||||||
id: "classifica",
|
id: "classifica",
|
||||||
|
|||||||
+65
-21
@@ -1,8 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { Cake, ChevronDown, Crown } from "lucide-react";
|
import { Cake, ChevronDown, Crown, SlidersHorizontal, Trophy } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PageHeader } from "@/components/crapp/ui-bits";
|
import { PageHeader, Select, StatTile } from "@/components/crapp/ui-bits";
|
||||||
import { BarraSottosezioni } from "@/components/crapp/BarraSottosezioni";
|
import { BarraSottosezioni } from "@/components/crapp/BarraSottosezioni";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
import { Avatar } from "@/components/crapp/Avatar";
|
||||||
import { formatData } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
@@ -13,11 +13,17 @@ import { totaliSquadra, useScoutMatches } from "@/lib/scout-store";
|
|||||||
import { useCsi } from "@/lib/csi";
|
import { useCsi } from "@/lib/csi";
|
||||||
import { partiteGiocate } from "@/lib/csi-core";
|
import { partiteGiocate } from "@/lib/csi-core";
|
||||||
import { mediaSquadra, usePagelle } from "@/lib/pagelle";
|
import { mediaSquadra, usePagelle } from "@/lib/pagelle";
|
||||||
import { StatTile } from "@/components/crapp/ui-bits";
|
|
||||||
import { Reveal } from "@/components/motion/Reveal";
|
import { Reveal } from "@/components/motion/Reveal";
|
||||||
import { Barra } from "@/components/motion/Barra";
|
import { Barra } from "@/components/motion/Barra";
|
||||||
import { Numero } from "@/components/motion/Numero";
|
import { Numero } from "@/components/motion/Numero";
|
||||||
import { BadgeDrawer } from "@/components/crapp/BadgeDrawer";
|
import { BadgeDrawer } from "@/components/crapp/BadgeDrawer";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
} from "@/components/ui/drawer";
|
||||||
import {
|
import {
|
||||||
badgeDefs,
|
badgeDefs,
|
||||||
badgeGiocatore,
|
badgeGiocatore,
|
||||||
@@ -80,6 +86,7 @@ function Squadra() {
|
|||||||
const { voti: pagelle } = usePagelle();
|
const { voti: pagelle } = usePagelle();
|
||||||
const [aperto, setAperto] = useState<string | null>(null);
|
const [aperto, setAperto] = useState<string | null>(null);
|
||||||
const [criterio, setCriterio] = useState<Criterio>("presenze");
|
const [criterio, setCriterio] = useState<Criterio>("presenze");
|
||||||
|
const [filtroAperto, setFiltroAperto] = useState(false);
|
||||||
const obiettivi = useObiettivi();
|
const obiettivi = useObiettivi();
|
||||||
const team = totaliSquadra(scoutMatches);
|
const team = totaliSquadra(scoutMatches);
|
||||||
const mediaPresenze = rosa.length
|
const mediaPresenze = rosa.length
|
||||||
@@ -102,6 +109,7 @@ function Squadra() {
|
|||||||
|
|
||||||
<BarraSottosezioni
|
<BarraSottosezioni
|
||||||
defaultId="rosa"
|
defaultId="rosa"
|
||||||
|
variante="sottolineatura"
|
||||||
voci={[
|
voci={[
|
||||||
{
|
{
|
||||||
id: "rosa",
|
id: "rosa",
|
||||||
@@ -262,32 +270,68 @@ function Squadra() {
|
|||||||
{
|
{
|
||||||
id: "classifica-giocatori",
|
id: "classifica-giocatori",
|
||||||
label: "Classifica",
|
label: "Classifica",
|
||||||
|
nascondiTitolo: true,
|
||||||
contenuto: (
|
contenuto: (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 flex flex-nowrap gap-1.5">
|
<div className="flex min-w-0 items-center gap-2 border-b border-border bg-card px-5 py-2.5">
|
||||||
{criteri.map((c) => (
|
<Trophy className="h-4 w-4 shrink-0 text-warning" aria-hidden />
|
||||||
<button
|
<span className="shrink-0 text-sm text-foreground/80">Classifica per</span>
|
||||||
key={c.id}
|
<span className="min-w-0 flex-1">
|
||||||
type="button"
|
<Select
|
||||||
onClick={() => setCriterio(c.id)}
|
value={criterio}
|
||||||
aria-pressed={criterio === c.id}
|
onChange={(e) => setCriterio(e.target.value as Criterio)}
|
||||||
className={cn(
|
aria-label="Criterio della classifica interna"
|
||||||
"min-h-11 min-w-0 flex-1 truncate rounded-full px-1.5 text-center text-xs font-bold uppercase transition-colors",
|
className="h-9 rounded-lg border-border bg-background font-semibold shadow-none"
|
||||||
criterio === c.id
|
|
||||||
? "bg-accent text-accent-foreground shadow-pop"
|
|
||||||
: "bg-secondary text-muted-foreground",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{c.label}
|
{criteri.map((c) => (
|
||||||
</button>
|
<option key={c.id} value={c.id}>
|
||||||
))}
|
{c.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFiltroAperto(true)}
|
||||||
|
className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-border text-muted-foreground"
|
||||||
|
aria-label="Scegli criterio classifica"
|
||||||
|
>
|
||||||
|
<SlidersHorizontal className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
|
<Drawer open={filtroAperto} onOpenChange={setFiltroAperto}>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Classifica per</DrawerTitle>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="flex flex-col gap-1 px-4 pb-6">
|
||||||
|
{criteri.map((c) => (
|
||||||
|
<DrawerClose key={c.id} asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCriterio(c.id)}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-11 w-full items-center rounded-xl px-3 text-left text-sm font-bold",
|
||||||
|
criterio === c.id
|
||||||
|
? "bg-accent text-accent-foreground"
|
||||||
|
: "bg-secondary text-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{c.label}
|
||||||
|
</button>
|
||||||
|
</DrawerClose>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<div className="space-y-2 px-5 pt-3">
|
||||||
{ordinati.map((g, i) => (
|
{ordinati.map((g, i) => (
|
||||||
<div key={g.id} className="rounded-2xl bg-card p-3 shadow-card">
|
<div key={g.id} className="rounded-2xl bg-card p-3 shadow-card">
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-secondary font-display text-base">
|
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-secondary font-display text-base">
|
||||||
{i + 1}
|
{i + 1}
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user