Rifacimento della UI secondo STILE_UI.md
Adotta lo stile condiviso delle app Integry: superfici piatte, raggi ampi, spaziature a scala fissa, gerarchia data dallo spazio prima che dalla decorazione. La palette storica di SteUP resta intatta; sono stati aggiunti solo i ruoli che mancavano (workspace, surfaceMuted, divider, inkMuted e le triplette dei toni semantici). Fondamenta - tokens.css: ruoli colore, scale di spazio/raggi/tipografia, curve di movimento e scala z-index semantica. Unica fonte di verita': pilotano anche le variabili CSS di MudBlazor. - Inter variabile ospitata in locale (133KB) al posto di Nunito da Google Fonts: l'app lavora offline, un font che arriva dalla rete in campo non arriva. - base.css: shell applicativa e comportamenti da app nativa — niente selezione testo, niente menu al tocco lungo, niente lampo grigio sul tap, niente rimbalzo elastico, niente zoom a doppio tocco, scroll con inerzia. - mudblazor-overrides.css: MudBlazor appiattito (elevation 0, nessun tint, target 44/48, etichette in caso naturale). - Rimossi Bootstrap (CSS, icone e JS) e i 996 righe di CSS con ambito per componente del vecchio sistema: erano tutti inutilizzati. Struttura - Navigazione flottante in basso con icona piena sulla voce attiva; da 720px diventa colonna laterale. Il MudFabMenu e' sostituito da un bottom sheet: stessa funzione, bersagli molto piu' grandi. - Testata alta col titolo a sinistra, fusa con la pagina. - Nel form scheda il salvataggio sta in una barra ancorata in fondo, non in coda a uno scroll lungo. - Scelta di punto vendita e articoli a schermo intero con ricerca; la selezione multipla usa righe da 56px invece di un menu a tendina. - Urgenza scelta a pillole invece che da una select. Stato e riscontro - StatusBadgeHelper: tono, icona ed etichetta di ogni stato in un punto solo, cosi' lo stesso stato si presenta identico ovunque. - Scheletri al posto degli spinner; l'overlay bloccante dice sempre cosa sta facendo; gli stati vuoti suggeriscono la mossa successiva. - Rimosso il ritardo artificiale di 250ms in InspectionCard, che non attendeva nulla. DESIGN.md riscritto sul nuovo sistema. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+304
@@ -0,0 +1,304 @@
|
||||
# Specifiche grafiche — stile UI condiviso
|
||||
|
||||
Documento di riferimento per **uniformare la grafica delle app Integry** allo stile adottato in Fixiy.
|
||||
|
||||
Descrive *forma, ritmo, tipografia e composizione dei componenti*. **Non contiene una palette**: ogni app mantiene i propri colori e li mappa sui **ruoli semantici** elencati al §2. Lo stile è quello che rimane costante al variare della tinta: superfici piatte, raggi ampi, spaziature a scala fissa, gerarchia data dallo spazio prima che dalla decorazione.
|
||||
|
||||
Riferimento d'implementazione: Flutter + Material 3 (`useMaterial3: true`). I nomi dei token qui sotto sono quelli reali del codice Fixiy (`lib/core/design_system.dart`, `lib/core/feedback.dart`); in un'altra app si rinominano col prefisso dell'app, ma **valori e semantica restano invariati**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Principi
|
||||
|
||||
1. **Superfici piatte.** Niente ombre per costruire gerarchia: `elevation: 0` ovunque (card, AppBar, NavigationBar, NavigationRail). L'unica ombra ammessa è quella del toast (elevation 6) e dei livelli modali di sistema. La separazione si ottiene con *contrasto di superficie* (sfondo pagina più freddo delle superfici) e con lo **spazio**.
|
||||
2. **Nessun tint automatico.** `surfaceTintColor: Colors.transparent` su AppBar, Card, Dialog, pannelli: le superfici non devono virare verso il colore primario quando si scrolla.
|
||||
3. **Colore = significato.** L'accento primario serve solo ad azioni, selezione, focus e progresso — mai a decorare. I colori semantici (urgenza, stato, esito) compaiono sempre accompagnati da **icona + etichetta testuale**, mai da soli.
|
||||
4. **Target generosi.** Ogni elemento interattivo ha lato minimo **44 px** (`minimumSize: Size(44, 44)`), 48 px d'altezza per i bottoni principali. L'app si usa in piedi, con i guanti, in negozio.
|
||||
5. **Densità standard.** `visualDensity: VisualDensity.standard`: si rifiuta la densità desktop, anche su schermo largo.
|
||||
6. **Lo spazio prima della linea.** I divisori si usano solo dove l'informazione cambia natura (testata di uno sheet, righe di una lista di navigazione); altrove separa la spaziatura.
|
||||
7. **Nulla di stock.** Si evitano le firme visive Android di default: card con ombra, FAB circolare al centro, AppBar colorata con titolo centrato, snackbar di sistema.
|
||||
8. **Scrivibile a voce alta.** Testi in lingua dell'app, brevi, orientati all'azione; gli enum passano da una mappa di etichette centralizzata (`labels.dart`), mai formattati inline.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ruoli colore (da riempire con la palette dell'app)
|
||||
|
||||
L'app ospite definisce i **propri** valori per questi ruoli e li usa con la stessa semantica. Nessun valore è fissato qui.
|
||||
|
||||
| Ruolo | Uso |
|
||||
|---|---|
|
||||
| `primary` | accento unico di azione: bottone pieno, focus, selezione, indicatori di progresso, icone attive |
|
||||
| `primaryDark` | testo/icona sopra `primaryContainer`, valori numerici in evidenza |
|
||||
| `workspace` | sfondo dell'applicazione (`scaffoldBackgroundColor`), leggermente più freddo/scuro di `surface` |
|
||||
| `surface` | superficie di lavoro: card, pannelli, AppBar, barra di navigazione, dialog |
|
||||
| `surfaceMuted` | superficie secondaria dentro una superficie (campo di ricerca, chip non selezionato, riquadro informativo interno) |
|
||||
| `divider` | bordi 1 px, separatori, contorno dei controlli a riposo |
|
||||
| `ink` | testo principale |
|
||||
| `inkMuted` | testo secondario, hint, etichette di metadato, icone non attive |
|
||||
| `emergency` / `errorSurface` | tono **danger**: errori, azioni distruttive, priorità massima |
|
||||
| `high` / `warningSurface` | tono **warning**: attenzione, dati incompleti, priorità alta |
|
||||
| `success` / `successSurface` | tono **success**: conferme, completamenti |
|
||||
| `info` / `infoSurface` | tono **info**: stato informativo, pannello di riepilogo, indicatore di selezione |
|
||||
| `neutral` / `surfaceMuted` | tono **neutral**: stati senza carica semantica |
|
||||
|
||||
**Regola delle coppie.** Ogni tono semantico è una coppia *(foreground forte, superficie tenue)*. Le superfici tenui riempiono badge, pannelli di feedback e cerchi-icona dei dialog; il foreground forte colora icona e testo sopra di esse. I **toast** invertono la regola: riempimento pieno col colore forte e testo bianco, per essere leggibili sopra qualunque contenuto.
|
||||
|
||||
**Schema colore.** Si genera da un seme (`ColorScheme.fromSeed`) e poi si sovrascrivono esplicitamente `primary`, `onPrimary`, `primaryContainer` (= `infoSurface`), `onPrimaryContainer` (= `primaryDark`), `secondaryContainer` (= `surfaceMuted`), `outline` e `outlineVariant` (= `divider`). Non si lascia al seme la scelta dei contenitori: devono restare tenui e neutri.
|
||||
|
||||
---
|
||||
|
||||
## 3. Scale di token
|
||||
|
||||
### Spaziatura (`Space`)
|
||||
|
||||
```
|
||||
xs = 8 sm = 12 md = 16 lg = 24 xl = 32
|
||||
```
|
||||
|
||||
Tutti i margini e le distanze sono multipli di 2 e, per quanto possibile, uno di questi cinque valori. Valori d'uso frequente derivati: 4 e 6 per le micro-distanze interne (titolo→sottotitolo, icona→testo), 10 tra le card di una lista, 18/20/22 per il padding dei pannelli di testata.
|
||||
|
||||
### Raggi (`Radius`)
|
||||
|
||||
```
|
||||
compact = 8 riquadri informativi, barre di progresso, feedback inline
|
||||
control = 16 campi di testo, bottoni, toast
|
||||
surface = 20 card, pannelli, dialog, sheet
|
||||
pill = 999 badge, chip, segmented button, campo di ricerca, bottoni onboarding
|
||||
```
|
||||
|
||||
Il contrasto tra i raggi è voluto: **superfici più arrotondate dei controlli**, elementi di stato completamente stondati (pill).
|
||||
|
||||
### Breakpoint
|
||||
|
||||
```
|
||||
rail = 720 da qui la navigazione passa da barra inferiore a NavigationRail laterale
|
||||
twoColumn = 900 soglia per layout a due colonne
|
||||
contentMax = 1180 larghezza massima assoluta del contenuto
|
||||
```
|
||||
|
||||
Larghezza tipica di una pagina: `maxWidth: 920`, centrata in alto (`PageWidth` = `Align(topCenter) + ConstrainedBox`). Su schermi larghi il contenuto **non si allarga all'infinito**: resta una colonna leggibile.
|
||||
|
||||
---
|
||||
|
||||
## 4. Tipografia
|
||||
|
||||
Famiglia principale **Inter**, fallback `['SF Pro Display', 'Roboto', 'sans-serif']`. Scala fissa, pensata per la scansione rapida, non per l'effetto editoriale:
|
||||
|
||||
| Stile | Dim. | Interlinea | Peso | Uso |
|
||||
|---|---|---|---|---|
|
||||
| `headlineMedium` | 34 | 1.2 | 700 | titoli di apertura |
|
||||
| `headlineSmall` | 28 | 1.25 | 700 | titolo dell'AppBar, valore percentuale in evidenza |
|
||||
| `titleLarge` | 22 | 1.3 | 700 | titolo di card e di sheet, testata di sezione |
|
||||
| `titleMedium` | 18 | 1.35 | 700 | sotto-titoli, valori metrica, titolo dialog |
|
||||
| `bodyLarge` | 16 | 1.5 | 400 | testo corrente, descrizioni |
|
||||
| `bodyMedium` | 15 | 1.45 | 400 | metadati, righe secondarie |
|
||||
| `labelLarge` | 14 | 1.25 | 600 | etichette dei bottoni |
|
||||
| `labelMedium` | 13 | 1.25 | 600 | etichette di navigazione, caption |
|
||||
|
||||
Regole d'uso:
|
||||
|
||||
- Il **titolo di una card è l'entità**, non il tipo di record (es. il negozio, non "Manutenzione"): `titleLarge` con peso 800 e interlinea compressa 1.15.
|
||||
- Gli **identificativi tecnici** (numero ticket, codice) vanno sopra il titolo, 12 px, peso 700, colore `inkMuted`, `letterSpacing: 0.3`.
|
||||
- Le **righe di metadato** si compongono con separatore ` · ` (spazio–punto medio–spazio) e colore `inkMuted`.
|
||||
- Il testo secondario non usa mai un peso inferiore a 400 né opacità: si distingue per **colore** (`inkMuted`), non per trasparenza.
|
||||
- Nessun maiuscoletto forzato, nessun `letterSpacing` ampio: le etichette restano in caso naturale.
|
||||
|
||||
---
|
||||
|
||||
## 5. Tema — impostazioni globali
|
||||
|
||||
| Elemento | Configurazione |
|
||||
|---|---|
|
||||
| **Scaffold** | sfondo `workspace`; `extendBody: true` quando la barra di navigazione è flottante |
|
||||
| **AppBar** | `toolbarHeight: 80`, titolo a sinistra (`centerTitle: false`), `titleSpacing` 20 (28 su schermo largo), sfondo `workspace` (si fonde con la pagina), elevation 0, `scrolledUnderElevation: 0` |
|
||||
| **Card / pannelli** | elevation 0, `margin: EdgeInsets.zero`, sfondo `surface`, raggio `surface`, senza bordo |
|
||||
| **Campi di testo** | `filled: true` su `surface`, padding 16×16, raggio `control`; bordo a riposo 1 px `divider`, in focus 2 px `primary`, in errore 1 px `emergency`; hint e helper in `inkMuted` |
|
||||
| **Bottone pieno** | `minimumSize: (44, 48)`, sfondo `primary`, testo bianco `labelLarge`, raggio `control` |
|
||||
| **Bottone contornato** | `minimumSize: (44, 48)`, testo `ink`, bordo `divider`, raggio `control` |
|
||||
| **Bottone testuale / icona** | `minimumSize: (44, 44)` |
|
||||
| **Segmented button** | `StadiumBorder`, min `(52, 44)`; selezionato = riempimento `primary` + testo bianco; non selezionato = `surface` + testo `inkMuted`; bordo `divider` |
|
||||
| **NavigationBar** | altezza 68, elevation 0, sfondo `surface`, indicatore `infoSurface`, etichette sempre visibili (`alwaysShow`), stile `labelMedium` |
|
||||
| **NavigationRail** | sfondo `surface`, indicatore `primaryContainer`, etichette su tutte le voci (`labelType: all`), etichetta selezionata in `primary` |
|
||||
| **Snackbar** | comportamento `floating` (ma nell'app si usa il toast custom, §6.9) |
|
||||
| **Progress** | colore `primary` |
|
||||
|
||||
**Localizzazione.** Locale unico dichiarato esplicitamente con i tre delegati globali (Material, Widgets, Cupertino), così date, picker e messaggi di sistema parlano la lingua dell'app.
|
||||
|
||||
---
|
||||
|
||||
## 6. Componenti
|
||||
|
||||
### 6.1 `PageWidth` — contenitore di pagina
|
||||
|
||||
`Align(topCenter)` + `ConstrainedBox(maxWidth: 920)`. Avvolge **ogni** contenuto di pagina. Su telefono è trasparente; su tablet/desktop impedisce righe di testo lunghissime e mantiene la stessa composizione a tutte le larghezze.
|
||||
|
||||
### 6.2 `Panel` — superficie base
|
||||
|
||||
`Material` con `color: surface`, `surfaceTintColor: transparent`, `clipBehavior: antiAlias`, forma arrotondata `surface` (20), padding di default 16.
|
||||
|
||||
È il mattone di tutto: card di lista, riquadri di riepilogo, righe raggruppate, campi compositi. Varianti d'uso:
|
||||
|
||||
- **con colore**: `color: infoSurface` per i pannelli di riepilogo/testata; `color: surfaceMuted` per i sotto-riquadri dentro un form; `color: errorSurface` per una card in stato critico.
|
||||
- **`padding: EdgeInsets.zero` + `InkWell` interno** quando l'intera superficie è toccabile (raggio dell'ink coerente con quello del pannello).
|
||||
|
||||
### 6.3 `SectionHeader` — testata di sezione
|
||||
|
||||
Riga con, a sinistra, `titleLarge` + eventuale descrizione (`inkMuted`, 4 px sotto), e a destra un widget opzionale (bottone, badge, contatore) separato da 12 px. Apre ogni blocco tematico dentro una pagina lunga.
|
||||
|
||||
### 6.4 `Badge` — stato in pillola
|
||||
|
||||
`DecoratedBox` con raggio 999, superficie tenue del tono, padding 9×5, contenuto = icona 15 px + 5 px + testo 12 px peso 700, entrambi nel foreground del tono. Avvolto in `Semantics(label:)`.
|
||||
|
||||
I badge **si costruiscono da funzioni di mappatura** (`priorityBadge(priority)`, `statusBadge(status)`), non a mano nelle pagine: così ogni schermata mostra lo stesso stato con la stessa icona e lo stesso tono. Ogni nuovo stato si aggiunge a quella funzione.
|
||||
|
||||
### 6.5 Card di elenco
|
||||
|
||||
Composizione canonica, dall'alto:
|
||||
|
||||
1. **Barra di priorità** verticale a tutta altezza sul bordo sinistro, larghezza **6 px**, colore dal tono di urgenza (`Stack` + `Positioned(top/bottom/left: 0)`). La card usa padding asimmetrico `(22, 16, 16, 16)` per far respirare la barra.
|
||||
2. **Identificativo** (12 px, `inkMuted`, 700) con, a destra, l'eventuale maniglia di trascinamento.
|
||||
3. **Titolo** = l'entità, `titleLarge` peso 800.
|
||||
4. **Riga meta** con separatori ` · `, `bodyMedium` peso 600 `inkMuted`.
|
||||
5. **Badge** (priorità, stato) in un `Wrap(spacing: 8, runSpacing: 8)`.
|
||||
6. **Descrizione** integrale, `bodyLarge` interlinea 1.35 — niente troncamenti sul contenuto che serve a decidere.
|
||||
7. **Riga oraria/assegnatario** preceduta da icona 16 px `inkMuted`.
|
||||
8. **Barra azioni interna**: secondaria contornata + primaria piena, affiancate con `Expanded`; sotto i **350 px** di larghezza della card diventano una colonna a piena larghezza (`LayoutBuilder`).
|
||||
|
||||
La card intera espone un `Semantics(label:)` che riassume entità, priorità e stato.
|
||||
|
||||
### 6.6 Pannello di riepilogo
|
||||
|
||||
Primo elemento della pagina, `Panel` con superficie tenue (`primaryContainer`/`infoSurface`), padding 18:
|
||||
|
||||
- riga di testata: titolo `titleLarge` + data/sottotitolo `inkMuted`, e a destra un **badge di stato** del documento ("Da salvare" / "Salvato" / "Concluso");
|
||||
- eventuale fila di **chip** di scelta (`Wrap(spacing: 8, runSpacing: 8)`);
|
||||
- fila di **metriche** in `Expanded` uguali, ciascuna: icona 20 px in `primaryDark` → valore `titleMedium` → etichetta 12 px `inkMuted` su una riga sola con ellissi;
|
||||
- eventuale riga di stato in coda (`labelMedium` `inkMuted`), es. l'ora dell'ultimo salvataggio.
|
||||
|
||||
Quando c'è un progresso, si aggiunge una `LinearProgressIndicator` con `minHeight: 8` e `borderRadius: 8`, preceduta dal conteggio in chiaro ("3 di 7 completate") e dalla percentuale in `headlineSmall`.
|
||||
|
||||
### 6.7 Chip e filtri
|
||||
|
||||
- **Filtri**: `ChoiceChip` `StadiumBorder`, `showCheckmark: false`, **l'etichetta include il conteggio** (`Tutte · 12`). A riposo: superficie `surfaceMuted`, bordo `divider`, testo `inkMuted` peso 600. Selezionato: superficie `infoSurface`, bordo `primary`, testo `primaryDark`. Disposti in una riga orizzontale scorrevole alta **44 px**, spaziatura 8.
|
||||
- **Chip-entità** (selezione di un oggetto): `Material` + `InkWell` con `StadiumBorder`, padding 12×8, icona 16 px + etichetta peso 600 + dato secondario 12 px `inkMuted`. Selezionato = superficie `surface` e bordo `primaryDark`; non selezionato = `surfaceMuted` e bordo `divider`.
|
||||
|
||||
### 6.8 Campo di ricerca
|
||||
|
||||
Non usa il tema dei campi: è **completamente stondato** (raggio 999), riempito in `surfaceMuted`, **senza bordo** a riposo e in enabled, bordo 1.5 px `primary` solo in focus. Padding 18×14, icona di ricerca `inkMuted` come prefisso, pulsante "cancella" come suffisso solo quando c'è testo.
|
||||
|
||||
### 6.9 Toast
|
||||
|
||||
Sostituisce ogni snackbar di sistema. `SnackBar` `floating` con sfondo trasparente, elevation 0, padding zero e margine `(md, 0, md, md)`; il contenuto è un `Material` con **riempimento pieno del tono**, raggio `control`, elevation 6, ombra nera al 24 %, padding 14×12, e dentro: icona bianca 22 px → 12 px → testo bianco peso 600 interlinea 1.35 → eventuale azione testuale bianca peso 700.
|
||||
|
||||
Prima di mostrarlo si chiama `hideCurrentSnackBar()`: mai una coda di toast. Durata 4 s. Si espone come scorciatoie per tono (`success`/`error`/`warning`/`info`) più una funzione generica.
|
||||
|
||||
### 6.10 Dialog
|
||||
|
||||
`Dialog` su `surface`, raggio `surface`, `insetPadding` 24, `maxWidth: 420`, padding interno 24. Composizione verticale allineata a sinistra:
|
||||
|
||||
1. **cerchio-icona** 48×48 riempito con la superficie tenue del tono, icona del tono 26 px;
|
||||
2. 16 px → **titolo** `titleMedium`;
|
||||
3. 8 px → **messaggio** `inkMuted` interlinea 1.45;
|
||||
4. 24 px → **azioni**: due bottoni in `Expanded` (annulla contornato a sinistra, conferma pieno a destra, 12 px di distanza); se non c'è annullamento, un solo bottone a piena larghezza.
|
||||
|
||||
Le azioni distruttive colorano di `emergency` il bottone di conferma. Due sole funzioni di ingresso: conferma (ritorna booleano) e informativo (un solo bottone).
|
||||
|
||||
### 6.11 Feedback inline
|
||||
|
||||
Riquadro non modale per messaggi persistenti dentro la pagina: superficie tenue del tono, raggio `compact` (8), padding 12, icona 20 px del tono + 10 px + testo peso 600 del tono, allineati in alto. Marcato `Semantics(liveRegion: true)`. Si usa per errori di caricamento e avvisi che devono restare visibili, a differenza del toast che è effimero.
|
||||
|
||||
### 6.12 Stato vuoto
|
||||
|
||||
Centrato, padding 28, larghezza massima 420: icona 44 px `inkMuted` → 14 px → titolo `titleMedium` centrato → 6 px → messaggio `inkMuted` centrato → 18 px → azione facoltativa (bottone contornato). Il messaggio **suggerisce la mossa successiva**; se lo stato vuoto dipende da filtri, l'azione li azzera.
|
||||
|
||||
### 6.13 Barra azioni fissa
|
||||
|
||||
Ancorata in fondo alla pagina: `SafeArea(top: false)`, superficie `surface`, **bordo superiore 1 px** `divider`, padding `(16, 12, 16, 16)`. I bottoni sono in riga con `Expanded` e 12 px di distanza; sotto i **390 px** diventano una colonna a piena larghezza con 8 px di distanza.
|
||||
|
||||
### 6.14 Bottom sheet
|
||||
|
||||
Sempre `isScrollControlled: true`, `useSafeArea: true`, `showDragHandle: true`. Altezza **fissata a una frazione dello schermo**: ~0.78 per uno sheet di consultazione, ~0.92 per uno sheet di compilazione.
|
||||
|
||||
Struttura:
|
||||
|
||||
1. **testata**: padding `(20, 0, 8, 12)`, titolo `titleLarge` + sottotitolo `inkMuted` a sinistra, `IconButton` di chiusura a destra;
|
||||
2. eventuale **segmented button** a piena larghezza (`expandedInsets: EdgeInsets.zero`) quando lo sheet ha due modalità;
|
||||
3. `Divider(height: 1)` — l'unica linea di struttura;
|
||||
4. **corpo** in `Expanded`, scrollabile, padding 16. Con più modalità si usa `IndexedStack`, che preserva lo stato dei form.
|
||||
|
||||
### 6.15 Navigazione
|
||||
|
||||
- **Telefono**: `NavigationBar` **flottante** — avvolta in `SafeArea(minimum: (16, 0, 16, 12))` e in un `ClipRRect(borderRadius: 24)`, con `extendBody: true` sullo Scaffold, così il contenuto scorre sotto. Le pagine lasciano ~110–120 px di padding inferiore per non finirci sotto.
|
||||
- **Da 720 px**: `NavigationRail` a sinistra con etichette su tutte le voci, seguito da `VerticalDivider(width: 1)`.
|
||||
- Le destinazioni usano **icona outline a riposo e piena da selezionata**, con la stessa etichetta in entrambe le modalità.
|
||||
- Le sezioni si tengono vive in un `IndexedStack`: cambiare tab non ricostruisce né perde lo scroll.
|
||||
- Le **notifiche** stanno nelle `actions` dell'AppBar: `IconButton` con icona outline dentro un `Badge` numerico visibile solo se ci sono elementi non letti, e 12 px di margine destro.
|
||||
|
||||
### 6.16 Liste di navigazione (profilo, impostazioni)
|
||||
|
||||
Un `Panel` che contiene `ListTile` con `contentPadding: EdgeInsets.zero`, icona a sinistra, `Icon(chevron_right)` come trailing per le voci che aprono una pagina, e `Divider()` tra una voce e l'altra. Le voci distruttive (uscita, eliminazione) portano l'icona in `emergency` e non hanno chevron.
|
||||
|
||||
L'intestazione del profilo è un `Panel` con padding 22: `CircleAvatar` raggio 30 su superficie tenue con l'iniziale in 22 px peso 800, 16 px, poi nome `titleLarge` e riga secondaria `inkMuted`.
|
||||
|
||||
### 6.17 Campo composito (tappabile)
|
||||
|
||||
Per valori che si scelgono da un picker invece che digitare: `Panel` `surfaceMuted` con padding zero, `InkWell` interno, padding 14, dentro un'etichetta `labelMedium`, 5 px, e una riga icona 19 px + 7 px + valore con ellissi. Si comporta come un campo ma apre un selettore.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tema di onboarding (login e schermate pre-accesso)
|
||||
|
||||
Le schermate fuori dall'applicazione autenticata usano una **variante derivata** dello stesso tema (`base.copyWith(...)`), non un tema separato: cambia solo il registro, non la struttura.
|
||||
|
||||
- Sfondo pagina uniforme e neutro; primario = colore d'inchiostro (accento cromatico assente: l'attenzione è sul marchio).
|
||||
- Campi con riempimento bianco e bordo neutro; in focus bordo 2 px del colore d'inchiostro.
|
||||
- Bottone principale **a pillola** (`StadiumBorder`), altezza minima 52.
|
||||
- Composizione: `SafeArea` → `Center` → `SingleChildScrollView(padding: 24)` → `ConstrainedBox(maxWidth: 440)` → colonna `stretch`.
|
||||
- Ordine: logo (altezza 128, dentro `Semantics(image: true, label:)`) → 32 px → campi a 16 px di distanza → 24 px → eventuale messaggio d'errore centrato in `colorScheme.error` e `liveRegion` → 12 px → bottone di invio → 20 px → footer con versione.
|
||||
- Il bottone di invio mostra il caricamento **al posto dell'icona** (`SizedBox.square(dimension: 18)` con `CircularProgressIndicator(strokeWidth: 2)`), restando della stessa dimensione; durante il caricamento `onPressed: null`.
|
||||
- I campi dichiarano `autofillHints` dentro un `AutofillGroup` e incatenano `textInputAction: next`, con invio sull'ultimo.
|
||||
|
||||
---
|
||||
|
||||
## 8. Comportamenti trasversali
|
||||
|
||||
**Caricamento.** Al primo caricamento senza dati: `CircularProgressIndicator` centrato in un'area di almeno 48 px di padding verticale. Con dati già presenti non si sostituisce mai il contenuto con uno spinner: si aggiorna in silenzio. Ogni elenco è avvolto in un `RefreshIndicator` (pull-to-refresh).
|
||||
|
||||
**Errore di caricamento.** Non svuota la pagina: compare un **feedback inline** (§6.11) in tono danger sopra il contenuto, con l'istruzione per riprovare ("Trascina verso il basso per riprovare"). Gli errori di rete non bloccano la navigazione.
|
||||
|
||||
**Vuoto vs. filtrato.** Si distinguono: "non c'è nulla" e "i filtri non trovano nulla" hanno testi diversi, e il secondo offre l'azzeramento dei filtri.
|
||||
|
||||
**Riordino.** Nelle liste riordinabili solo gli elementi *ammissibili* mostrano la maniglia (`ReorderableDragStartListener` con `Icon(drag_handle)`); il riordino è disabilitato quando è attivo un filtro o una ricerca, e un suggerimento testuale compare accanto al conteggio solo sopra i 520 px di larghezza.
|
||||
|
||||
**Conteggi sempre visibili.** Titoli di sezione, filtri e badge portano il numero ("12 attività", "Allegati (3)", "Tutte · 12"): il tecnico deve sapere quanto ha davanti senza contare.
|
||||
|
||||
**Adattamento locale.** L'adattamento non è solo a livello di pagina: i **singoli componenti** misurano il proprio spazio con `LayoutBuilder` e passano da riga a colonna sotto le soglie indicate (350 px per le azioni di una card, 390 px per la barra azioni). Così lo stesso componente funziona in colonna singola e affiancato.
|
||||
|
||||
**Accessibilità.**
|
||||
- `Semantics(label:)` su card, badge e maniglie di riordino, con una descrizione che riassume l'entità e i suoi stati.
|
||||
- `Semantics(liveRegion: true)` su toast, feedback inline e messaggi d'errore dei form.
|
||||
- `tooltip` su ogni `IconButton` senza etichetta.
|
||||
- Nessuna informazione veicolata dal solo colore: sempre icona e/o testo.
|
||||
- Target ≥ 44 px anche per le icone in testata.
|
||||
|
||||
**Testi e stati.** Le date estese si scrivono per intero nella lingua dell'app; gli orari in `HH:mm` con zero iniziale, e i valori mancanti come `--:--`; i dati incompleti si dichiarano ("km finali mancanti", "Partenza da compilare") invece di lasciare un vuoto.
|
||||
|
||||
---
|
||||
|
||||
## 9. Come adottare lo stile in un'altra app
|
||||
|
||||
1. **Copiare la struttura dei token**: quattro classi statiche `Colors`, `Space`, `Radius`, `Breakpoints` con i valori dei §3 (spazi, raggi e breakpoint **identici**) e i propri colori sui ruoli del §2.
|
||||
2. **Ricostruire il tema** con le impostazioni del §5: sono la parte che rende immediatamente riconoscibile lo stile (AppBar alta e fusa con la pagina, card piatte a raggio 20, controlli a raggio 16, target 44/48).
|
||||
3. **Portare i componenti condivisi** del §6 — `PageWidth`, `Panel`, `SectionHeader`, `Badge`, `Feedback`, `EmptyState`, `ActionBar`, toast e dialog — rinominandoli col prefisso dell'app. Sono ~600 righe senza dipendenze oltre a Material.
|
||||
4. **Definire le mappature di stato** dell'app (`priorityBadge`, `statusBadge`, o gli equivalenti del dominio) in un unico punto, accanto ai componenti.
|
||||
5. **Vietare i valori ad-hoc**: nelle pagine si usano solo i token. Un colore o un raggio scritto a mano in una pagina è un difetto da correggere, non una scorciatoia.
|
||||
6. **Verificare la lista di controllo**:
|
||||
- [ ] nessuna ombra sulle superfici (elevation 0) e nessun `surfaceTint`;
|
||||
- [ ] sfondo pagina diverso dalle superfici;
|
||||
- [ ] ogni tocco ≥ 44 px;
|
||||
- [ ] ogni stato = icona + etichetta + tono;
|
||||
- [ ] contenuto limitato a ~920 px e centrato;
|
||||
- [ ] barra di navigazione flottante sotto i 720 px, rail sopra;
|
||||
- [ ] toast custom al posto delle snackbar di sistema;
|
||||
- [ ] stato vuoto, stato di caricamento e stato d'errore previsti per ogni elenco;
|
||||
- [ ] componenti che si riorganizzano da soli sotto le soglie di larghezza.
|
||||
Reference in New Issue
Block a user