Estende alla card degli eventi il fix delle date su iOS.

Il `min-w-0` di 366928b era finito solo in `ProfiloAmministrativo` perché
`Campo` e le classi degli input erano ricopiati identici in tre file: il
form «Nuovo evento» e la dashboard admin («Data tessera» in `grid-cols-2`)
sfondavano ancora la card. Ora `Campo` e `classiInput` stanno una volta
sola in `ui-bits` e le tre copie spariscono.

La regola CSS che rende ridimensionabili i controlli nativi copre anche
`input[type="time"]`, che nel form eventi sta affiancato alla data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 23:41:20 +02:00
co-authored by Claude Opus 5
parent 107407b854
commit 2a1a0839c0
6 changed files with 47 additions and 60 deletions
+9
View File
@@ -6,6 +6,15 @@ qui: sta in [ROADMAP.md](ROADMAP.md).
## Versione attuale — agosto 2026 ## Versione attuale — agosto 2026
### Form eventi: data e ora non sfondano più la card su iOS
- Lo stesso difetto già corretto sul tesseramento: `Campo` e le classi degli input erano
ricopiati identici in tre file, quindi il `min-w-0` aggiunto in `ProfiloAmministrativo`
non arrivava né al form «Nuovo evento» né alla dashboard admin. Ora `Campo` e
`classiInput` stanno una volta sola in `ui-bits`.
- La regola CSS che rende ridimensionabili i controlli nativi copre anche
`input[type="time"]`, che nel form eventi sta affiancato alla data in `grid-cols-2`.
### Profilo: tab Documenti e Opzioni, barra senza scroll ### Profilo: tab Documenti e Opzioni, barra senza scroll
- L'etichetta nominava lo scopo (il tesseramento CSI) invece del contenuto: dentro ci sono - L'etichetta nominava lo scopo (il tesseramento CSI) invece del contenuto: dentro ci sono
+1 -21
View File
@@ -3,7 +3,7 @@ import { Link } from "@tanstack/react-router";
import { Check, Eye, Loader2, Upload } from "lucide-react"; import { Check, Eye, Loader2, Upload } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { SezioneTendina } from "@/components/crapp/ui-bits"; import { Campo, classiInput, SezioneTendina } from "@/components/crapp/ui-bits";
import { Reveal } from "@/components/motion/Reveal"; import { Reveal } from "@/components/motion/Reveal";
import { import {
caricaFile, caricaFile,
@@ -22,26 +22,6 @@ import {
const TIPI_DOCUMENTO = ["Carta d'identità", "Patente", "Passaporto"]; const TIPI_DOCUMENTO = ["Carta d'identità", "Patente", "Passaporto"];
const classiInput =
"w-full min-w-0 rounded-xl border border-border bg-background px-3 py-2 text-sm";
/**
* `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 su iOS) la coppia
* sfonda la card.
*/
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
return (
<label className="block min-w-0">
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
{label}
</span>
<span className="mt-1 block">{children}</span>
</label>
);
}
function Intestazione({ titolo, completa }: { titolo: string; completa: boolean }) { function Intestazione({ titolo, completa }: { titolo: string; completa: boolean }) {
return ( return (
<div className="flex items-center gap-2 pt-2"> <div className="flex items-center gap-2 pt-2">
+21
View File
@@ -156,6 +156,27 @@ export function SezioneTendina({
); );
} }
/** Classi condivise di input, select e textarea nei form dell'app. */
export const classiInput =
"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 (
<label className="block min-w-0">
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
{label}
</span>
<span className="mt-1 block">{children}</span>
</label>
);
}
export function StatoBadge({ stato, className }: { stato: Stato; className?: string }) { export function StatoBadge({ stato, className }: { stato: Stato; className?: string }) {
const meta = statoMeta[stato]; const meta = statoMeta[stato];
return ( return (
+1 -14
View File
@@ -16,7 +16,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { PageHeader, Section, StatTile } from "@/components/crapp/ui-bits"; import { Campo, classiInput, PageHeader, Section, StatTile } from "@/components/crapp/ui-bits";
import { CampiProfilo } from "@/components/crapp/ProfiloAmministrativo"; import { CampiProfilo } from "@/components/crapp/ProfiloAmministrativo";
import { import {
nomeCompleto, nomeCompleto,
@@ -76,8 +76,6 @@ const statoClasse: Record<StatoScadenza | "presente" | "assente", string> = {
assente: "bg-secondary text-muted-foreground", assente: "bg-secondary text-muted-foreground",
}; };
const classiInput = "w-full rounded-xl border border-border bg-background px-3 py-2 text-sm";
/** L'unico vincolo unique lato database sulla tabella è l'email: messaggio leggibile invece /** L'unico vincolo unique lato database sulla tabella è l'email: messaggio leggibile invece
* del codice Postgres (23505). */ * del codice Postgres (23505). */
function messaggioErrore(e: unknown, fallback: string): string { function messaggioErrore(e: unknown, fallback: string): string {
@@ -87,17 +85,6 @@ function messaggioErrore(e: unknown, fallback: string): string {
return e instanceof Error ? e.message : fallback; return e instanceof Error ? e.message : fallback;
} }
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
return (
<label className="block">
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
{label}
</span>
<span className="mt-1 block">{children}</span>
</label>
);
}
/** /**
* Pannello di modifica dell'admin (DD-017): dati squadra, dati personali e collegamento * Pannello di modifica dell'admin (DD-017): dati squadra, dati personali e collegamento
* all'account. I file restano fuori: l'admin li scarica, non li carica al posto di altri. * all'account. I file restano fuori: l'admin li scarica, non li carica al posto di altri.
+6 -17
View File
@@ -3,7 +3,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { ArrowLeft, CalendarPlus, Loader2, Pencil, Trash2 } from "lucide-react"; import { ArrowLeft, CalendarPlus, Loader2, Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { PageHeader, Section } from "@/components/crapp/ui-bits"; import { Campo, classiInput, PageHeader, Section } from "@/components/crapp/ui-bits";
import { formatData } from "@/lib/crapp-data"; import { formatData } from "@/lib/crapp-data";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { import {
@@ -151,7 +151,7 @@ function GestioneEventi() {
maxLength={80} maxLength={80}
onChange={(e) => aggiorna({ titolo: e.target.value })} onChange={(e) => aggiorna({ titolo: e.target.value })}
placeholder="Es. CRAP Volley vs Aurora Nera" placeholder="Es. CRAP Volley vs Aurora Nera"
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-sm" className={classiInput}
/> />
</Campo> </Campo>
@@ -161,7 +161,7 @@ function GestioneEventi() {
type="date" type="date"
value={bozza.data} value={bozza.data}
onChange={(e) => aggiorna({ data: e.target.value })} onChange={(e) => aggiorna({ data: e.target.value })}
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-sm" className={classiInput}
/> />
</Campo> </Campo>
<Campo label="Ora"> <Campo label="Ora">
@@ -169,7 +169,7 @@ function GestioneEventi() {
type="time" type="time"
value={bozza.ora} value={bozza.ora}
onChange={(e) => aggiorna({ ora: e.target.value })} onChange={(e) => aggiorna({ ora: e.target.value })}
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-sm" className={classiInput}
/> />
</Campo> </Campo>
</div> </div>
@@ -179,7 +179,7 @@ function GestioneEventi() {
value={bozza.luogo} value={bozza.luogo}
maxLength={80} maxLength={80}
onChange={(e) => aggiorna({ luogo: e.target.value })} onChange={(e) => aggiorna({ luogo: e.target.value })}
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-sm" className={classiInput}
/> />
</Campo> </Campo>
@@ -189,7 +189,7 @@ function GestioneEventi() {
maxLength={300} maxLength={300}
rows={2} rows={2}
onChange={(e) => aggiorna({ note: e.target.value })} onChange={(e) => aggiorna({ note: e.target.value })}
className="w-full rounded-xl border border-border bg-background px-3 py-2 text-sm" className={classiInput}
/> />
</Campo> </Campo>
@@ -322,17 +322,6 @@ function GestioneEventi() {
); );
} }
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
return (
<label className="block">
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
{label}
</span>
<span className="mt-1 block">{children}</span>
</label>
);
}
function Interruttore({ function Interruttore({
attivo, attivo,
onClick, onClick,
+9 -8
View File
@@ -126,7 +126,7 @@
--ring: oklch(0.35 0.03 265); --ring: oklch(0.35 0.03 265);
/* Luminosità scelte perché il testo bianco sopra superi 4.5:1 (WCAG AA). */ /* Luminosità scelte perché il testo bianco sopra superi 4.5:1 (WCAG AA). */
/* Compleanni / successi: verde un po' più chiaro e saturo. */ /* Compleanni / successi: verde un po' più chiaro e saturo. */
--success: oklch(0.60 0.17 152); --success: oklch(0.6 0.17 152);
--success-foreground: oklch(0.99 0 0); --success-foreground: oklch(0.99 0 0);
--warning: oklch(0.78 0.15 78); --warning: oklch(0.78 0.15 78);
--warning-foreground: oklch(0.18 0.02 60); --warning-foreground: oklch(0.18 0.02 60);
@@ -528,15 +528,16 @@
} }
/* /*
* Su iOS `input[type="date"]` è un controllo nativo con una larghezza * Su iOS `input[type="date"]` e `input[type="time"]` sono controlli nativi con
* intrinseca (lo spazio di «gg/mm/aaaa») che `width: 100%` non riesce a far * una larghezza intrinseca (lo spazio di «gg/mm/aaaa», «--:--») che
* scendere: dentro `grid-cols-2` due date affiancate sfondano la card, e * `width: 100%` non riesce a far scendere: dentro `grid-cols-2` due campi
* l'`overflow-hidden` del pannello a tab taglia via quello che esce. * affiancati sfondano la card, e l'`overflow-hidden` del pannello a tab taglia
* `appearance: none` è quello che rende il campo davvero ridimensionabile; * via quello che esce. `appearance: none` è quello che rende il campo davvero
* bordo, sfondo e padding li mette già la classe condivisa. * ridimensionabile; bordo, sfondo e padding li mette già la classe condivisa.
*/ */
@layer base { @layer base {
input[type="date"] { input[type="date"],
input[type="time"] {
min-width: 0; min-width: 0;
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;