Layout su schermi stretti: scout e griglie non tagliati a 360 px
Tabella scout senza larghezza minima (nome a capo, colonne compatte), griglie con min-width 0, niente scroll orizzontale della pagina, campi a 16px contro lo zoom iOS. Fixes #12 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b93f92965
commit
263621456d
5 files changed
+73
-11
No files matched your search
@@ -13,6 +13,13 @@ versioni `0.y.z` erano pre-release.
|
||||
|
||||
## [Non rilasciato]
|
||||
|
||||
### Modificato
|
||||
|
||||
- Layout su schermi stretti (360-402 px, iPhone e Android): niente più pagina tagliata in
|
||||
larghezza. La tabella per giocatore dello scout sta nello schermo (il nome si accorcia), le
|
||||
griglie di riquadri non sfondano con nomi ed etichette lunghe, la pagina non scorre in
|
||||
orizzontale e i campi di testo sono a 16 px, così iOS non zooma al tocco.
|
||||
|
||||
## [1.6.1] - 2026-11-06
|
||||
|
||||
Risultati dello scout leggibili nell'app, classifica cacche completa e pagina partita a sezioni
|
||||
|
||||
@@ -124,7 +124,7 @@ export function ScoutReport({
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="Periodo"
|
||||
className="mt-3 grid grid-flow-col auto-cols-fr gap-1 rounded-2xl bg-secondary p-1"
|
||||
className="mt-3 grid grid-flow-col auto-cols-fr gap-0.5 rounded-2xl bg-secondary p-1"
|
||||
>
|
||||
{[undefined, ...sets].map((s) => (
|
||||
<button
|
||||
@@ -134,7 +134,7 @@ export function ScoutReport({
|
||||
aria-selected={s === set}
|
||||
onClick={() => setSet(s)}
|
||||
className={cn(
|
||||
"min-w-0 truncate rounded-xl px-1 py-2 text-[13px] font-bold uppercase transition-colors",
|
||||
"min-w-0 truncate rounded-xl px-0 py-2 text-[13px] font-bold uppercase transition-colors",
|
||||
s === set
|
||||
? "bg-primary text-primary-foreground shadow-card"
|
||||
: "text-muted-foreground",
|
||||
@@ -185,13 +185,13 @@ export function ScoutReport({
|
||||
Nessuna valutazione in questo set.
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-2 overflow-x-auto rounded-2xl bg-secondary/60">
|
||||
<table className="w-full min-w-[22rem] text-[13px]">
|
||||
<div className="mt-2 overflow-hidden rounded-2xl bg-secondary/60">
|
||||
<table className="w-full table-fixed text-[13px]">
|
||||
<thead>
|
||||
<tr className="text-left text-[13px] uppercase text-muted-foreground">
|
||||
<th className="px-3 py-2 font-bold">Giocatore</th>
|
||||
<th className="whitespace-nowrap px-2 py-2 font-bold">Giocatore</th>
|
||||
{fondamentali.map((f) => (
|
||||
<th key={f.id} className="px-2 py-2 text-center font-bold">
|
||||
<th key={f.id} className="w-11 px-0 py-2 text-center font-bold">
|
||||
{f.label.slice(0, 3)}
|
||||
</th>
|
||||
))}
|
||||
@@ -200,16 +200,16 @@ export function ScoutReport({
|
||||
<tbody className="divide-y divide-border">
|
||||
{righe.map(({ g, conteggi }) => (
|
||||
<tr key={g.id}>
|
||||
<td className="max-w-40 truncate px-3 py-2 font-semibold">
|
||||
<td className="px-2 py-2 font-semibold leading-tight">
|
||||
<span className="mr-1.5 tabular-nums text-muted-foreground">
|
||||
#{g.numero}
|
||||
</span>
|
||||
{g.nome}
|
||||
</td>
|
||||
{fondamentali.map((f) => (
|
||||
<td key={f.id} className="px-2 py-2 text-center font-semibold tabular-nums">
|
||||
<td key={f.id} className="px-0 py-2 text-center font-semibold tabular-nums">
|
||||
<span className="text-success">{conteggi[f.id].piu}</span>
|
||||
<span className="text-muted-foreground"> / </span>
|
||||
<span className="text-muted-foreground">/</span>
|
||||
<span className="text-destructive">{conteggi[f.id].meno}</span>
|
||||
</td>
|
||||
))}
|
||||
@@ -238,7 +238,7 @@ export function ScoutReport({
|
||||
{ l: "Muri", v: totaliAzioni.muri },
|
||||
{ l: "Errori", v: totaliAzioni.errori },
|
||||
].map((t) => (
|
||||
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
||||
<div key={t.l} className="rounded-2xl bg-secondary px-1 py-2.5">
|
||||
<p className="font-display text-xl leading-none">{t.v}</p>
|
||||
<p className="mt-1 text-[13px] font-semibold uppercase text-muted-foreground">
|
||||
{t.l}
|
||||
|
||||
@@ -339,9 +339,11 @@ export function SezioneTendina({
|
||||
* `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.
|
||||
* `text-base` (16px) non è estetica: sotto i 16px iOS Safari zooma la pagina al focus e,
|
||||
* dopo il blur, la lascia ingrandita e tagliata in larghezza.
|
||||
*/
|
||||
export const classiInput =
|
||||
"h-10 w-full min-w-0 rounded-xl border border-border bg-background px-3 py-2 text-[15px]";
|
||||
"h-10 w-full min-w-0 rounded-xl border border-border bg-background px-3 py-2 text-base";
|
||||
|
||||
/**
|
||||
* Etichetta + controllo di un form. `min-w-0`: dentro `grid-cols-2` questa label
|
||||
|
||||
@@ -520,11 +520,31 @@
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mai scroll orizzontale della pagina (iPhone 360-402 px). `clip` e non `hidden`:
|
||||
* non crea un contenitore di scroll, quindi `position: sticky` continua a funzionare.
|
||||
*/
|
||||
html,
|
||||
body {
|
||||
max-width: 100%;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gli elementi di una griglia hanno `min-width: auto`: una colonna `1fr` non scende sotto
|
||||
* la parola più lunga e sfonda la riga. Con `min-width: 0` la colonna resta nel suo posto
|
||||
* e `overflow-wrap` spezza solo la parola che davvero non ci sta.
|
||||
*/
|
||||
.grid > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--font-sans);
|
||||
line-height: 1.5;
|
||||
overflow-wrap: break-word;
|
||||
font-optical-sizing: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Niente pull-to-refresh accidentale né rimbalzo che scopre il fondo. */
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Check dei requisiti di layout per schermi stretti (360 px): `bun test/unit/layout-stretto.test.ts`.
|
||||
*
|
||||
* Non c'è un driver browser (vedi `test/README.md`), quindi si presidiano le due regole che,
|
||||
* se tolte, fanno tornare la pagina tagliata in larghezza su iPhone: i campi a 16 px (sotto,
|
||||
* iOS zooma al focus e non torna indietro) e la rete di sicurezza sull'overflow orizzontale.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { classiInput } from "@/components/crapp/ui-bits";
|
||||
|
||||
assert.match(classiInput, /\btext-base\b/, "i campi devono essere a 16px (zoom automatico iOS)");
|
||||
assert.doesNotMatch(classiInput, /text-\[1[0-5]px\]|text-sm|text-xs/);
|
||||
|
||||
const css = readFileSync(new URL("../../src/styles.css", import.meta.url), "utf8");
|
||||
assert.match(css, /overflow-x:\s*clip/, "html/body devono impedire lo scroll orizzontale");
|
||||
assert.match(
|
||||
css,
|
||||
/\.grid\s*>\s*\*\s*\{\s*min-width:\s*0/,
|
||||
"gli elementi di griglia devono poter restringersi",
|
||||
);
|
||||
|
||||
const scout = readFileSync(
|
||||
new URL("../../src/components/crapp/ScoutReport.tsx", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
scout,
|
||||
/min-w-\[\d+rem\]/,
|
||||
"la tabella scout non deve imporre una larghezza minima",
|
||||
);
|
||||
|
||||
console.log("layout-stretto: ok");
|
||||
Reference in new issue
Block a user