Togli il + dalla barra e porta le azioni nella testata

Il "+" stava incastrato a destra nella barra di navigazione, che per il resto
contiene destinazioni. Cambiava significato da una schermata all'altra senza
mai dichiararlo, e su Ispezioni apriva un menu per far scegliere fra una cosa
sola. Il documento di stile esclude esplicitamente il FAB di serie, e la barra
torna a contenere solo le due destinazioni.

Su Ispezioni l'azione e' un bottone con il suo nome nella testata, e porta
dritta alla scelta del punto vendita senza passaggi intermedi. Lo stato vuoto
offre lo stesso comando invece di spiegare dove trovarlo.

Su Ispezione le azioni sono tre e con pesi diversi: restano in un bottom sheet,
aperto dalla testata. Eliminare un'ispezione ora chiede conferma — prima un
tocco cancellava ispezione e schede senza appello.

Le azioni vivono nella pagina che le esegue, quindi i messaggi che le
rimbalzavano dalla barra (NewSchedaService, CompleteInspectionService) non
hanno piu' mittente e se ne vanno.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 12:47:06 +02:00
parent 101b68123c
commit 2e3a318132
9 changed files with 254 additions and 266 deletions
-4
View File
@@ -17,9 +17,7 @@ using SteUp.Shared.Core.Interface.IntegryApi;
using SteUp.Shared.Core.Interface.LocalDb; using SteUp.Shared.Core.Interface.LocalDb;
using SteUp.Shared.Core.Interface.System; using SteUp.Shared.Core.Interface.System;
using SteUp.Shared.Core.Interface.System.Network; using SteUp.Shared.Core.Interface.System.Network;
using SteUp.Shared.Core.Messages.Ispezione;
using SteUp.Shared.Core.Messages.Scanner; using SteUp.Shared.Core.Messages.Scanner;
using SteUp.Shared.Core.Messages.Scheda;
using SteUp.Shared.Core.Messages.System; using SteUp.Shared.Core.Messages.System;
using SteUp.Shared.Core.Services; using SteUp.Shared.Core.Services;
@@ -63,9 +61,7 @@ public static class CoreModule
public void RegisterMessageServices() public void RegisterMessageServices()
{ {
builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>(); builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>();
builder.Services.AddSingleton<NewSchedaService>();
builder.Services.AddSingleton<OnScannerService>(); builder.Services.AddSingleton<OnScannerService>();
builder.Services.AddSingleton<CompleteInspectionService>();
builder.Services.AddSingleton<BackButtonService>(); builder.Services.AddSingleton<BackButtonService>();
} }
+7 -172
View File
@@ -1,15 +1,3 @@
@using CommunityToolkit.Mvvm.Messaging
@using SteUp.Shared.Components.Layout.Sheet
@using SteUp.Shared.Components.SingleElements.MessageBox
@using SteUp.Shared.Core.Enum
@using SteUp.Shared.Core.Interface.LocalDb
@using SteUp.Shared.Core.Messages.Ispezione
@using SteUp.Shared.Core.Messages.Scheda
@inject INetworkService NetworkService
@inject IDialogService Dialog
@inject IMessenger Messenger
@inject IIspezioniService IspezioniService
@inject IJSRuntime Js
@implements IDisposable @implements IDisposable
@* @*
@@ -17,6 +5,10 @@
sotto, le destinazioni restano a portata di pollice. Icona outline a sotto, le destinazioni restano a portata di pollice. Icona outline a
riposo, piena da selezionata, etichetta sempre visibile in entrambe. riposo, piena da selezionata, etichetta sempre visibile in entrambe.
Da 720px diventa una colonna laterale (vedi components.css). Da 720px diventa una colonna laterale (vedi components.css).
Qui stanno SOLO le destinazioni. Le azioni di una schermata appartengono
alla schermata, e si trovano nella sua testata: un "+" incastrato nella
barra non dice cosa crea, e cambia significato da una pagina all'altra.
*@ *@
<nav class="navbar @(IsVisible ? "" : "navbar--hidden")" aria-label="Navigazione principale"> <nav class="navbar @(IsVisible ? "" : "navbar--hidden")" aria-label="Navigazione principale">
@@ -33,189 +25,32 @@
</span> </span>
<span class="navbar__label">Profilo</span> <span class="navbar__label">Profilo</span>
</NavLink> </NavLink>
@if (ActionsVisible)
{
<button type="button" class="navbar__fab" @onclick="OpenActions"
aria-label="Azioni" title="Azioni">
<i class="ri-add-line" aria-hidden="true"></i>
</button>
}
</nav> </nav>
<ActionSheet @bind-Open="_sheetOpen" Title="@SheetTitle" Subtitle="@SheetSubtitle">
<div class="stack stack-xs">
@if (OnInspectionPage)
{
@if (!_isCompleted)
{
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="NewScheda">
<span class="action-tile__icon action-tile__icon--primary">
<i class="ri-add-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Nuova scheda</span>
</button>
}
@if (ShowCompleteInspection)
{
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="CompleteInspection">
<span class="action-tile__icon action-tile__icon--success">
<i class="ri-check-double-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Concludi ispezione</span>
</button>
}
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="DeleteInspection">
<span class="action-tile__icon action-tile__icon--danger">
<i class="ri-delete-bin-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Elimina ispezione</span>
</button>
}
else
{
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="NewInspection">
<span class="action-tile__icon action-tile__icon--primary">
<i class="ri-add-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Nuova ispezione</span>
</button>
}
@if (!NetworkService.IsNetworkAvailable())
{
<div class="feedback feedback--warning" role="status">
<i class="feedback__icon ri-wifi-off-line" aria-hidden="true"></i>
<span>Senza connessione queste azioni non sono disponibili. Il lavoro gia' fatto resta sul dispositivo.</span>
</div>
}
</div>
</ActionSheet>
<ConfirmMessageBox @ref="_messageBox" NoText="Annulla" YesText="Concludi" YesColor="Color.Primary"
Message="Una volta conclusa, l'ispezione non sara' piu' modificabile e non potrai aggiungere altre schede. Procedere?"/>
@code @code
{ {
private bool IsVisible { get; set; } = true; private bool IsVisible { get; set; } = true;
private bool ActionsVisible { get; set; } = true;
private bool OnInspectionPage { get; set; }
private bool ShowCompleteInspection { get; set; }
private bool _isCompleted;
private bool _sheetOpen;
private ConfirmMessageBox _messageBox = null!;
private static readonly string[] NavigableRoutes = ["ispezioni", "ispezione", "user"]; private static readonly string[] NavigableRoutes = ["ispezioni", "ispezione", "user"];
private string SheetTitle => OnInspectionPage ? "Azioni ispezione" : "Nuova ispezione";
private string? SheetSubtitle => OnInspectionPage
? SteupDataService.InspectionPageState.Ispezione.CodMdep
: null;
private string CurrentRoute => private string CurrentRoute =>
NavigationManager.Uri.Remove(0, NavigationManager.BaseUri.Length).Split('?')[0]; NavigationManager.Uri.Remove(0, NavigationManager.BaseUri.Length).Split('?')[0];
private bool IsOn(string route) => CurrentRoute.EqualsIgnoreCase(route); private bool IsOn(string route) => CurrentRoute.EqualsIgnoreCase(route);
protected override Task OnInitializedAsync() protected override void OnInitialized()
{ {
NavigationManager.LocationChanged += OnLocationChanged; NavigationManager.LocationChanged += OnLocationChanged;
Refresh(); Refresh();
return Task.CompletedTask;
} }
private void OnLocationChanged(object? sender, LocationChangedEventArgs args) private void OnLocationChanged(object? sender, LocationChangedEventArgs args) => Refresh();
{
// Cambiando schermata un menu aperto non ha piu' senso.
_sheetOpen = false;
Refresh();
}
private void Refresh() private void Refresh()
{ {
var route = CurrentRoute; IsVisible = NavigableRoutes.Contains(CurrentRoute, StringComparer.OrdinalIgnoreCase);
IsVisible = NavigableRoutes.Contains(route, StringComparer.OrdinalIgnoreCase);
OnInspectionPage = route.EqualsIgnoreCase("ispezione");
ActionsVisible = IsVisible && !route.EqualsIgnoreCase("user");
if (OnInspectionPage)
{
var ispezione = SteupDataService.InspectionPageState.Ispezione;
_isCompleted = ispezione.Stato == StatusEnum.Completata;
// "Concludi" non ha senso su un'ispezione mai inviata o gia' conclusa.
ShowCompleteInspection = !ispezione.ActivityId.IsNullOrEmpty() && !_isCompleted;
// Su un'ispezione conclusa resta solo la consultazione.
if (_isCompleted) ActionsVisible = false;
}
StateHasChanged(); StateHasChanged();
} }
private void OpenActions()
{
Refresh();
_sheetOpen = true;
}
private async Task NewInspection()
{
_sheetOpen = false;
if (await SteupDataService.CanOpenNewInspection())
{
_ = Dialog.OpenSelectShop();
}
else
{
await Dialog.ShowWarning(
"Per aprire una nuova ispezione devi prima concludere tutte le ispezioni aperte da piu' di 20 giorni.");
}
}
private async Task CompleteInspection()
{
_sheetOpen = false;
var result = await _messageBox.ShowAsync();
if (result is true) Messenger.Send(new CompleteInspectionMessage());
}
private async Task NewScheda()
{
_sheetOpen = false;
var ispezione = SteupDataService.InspectionPageState.Ispezione;
var modal = await Dialog.OpenFormScheda(ispezione.CodMdep, ispezione.Data, true);
if (modal is { Canceled: false }) Messenger.Send(new NewSchedaMessage());
}
private async Task DeleteInspection()
{
_sheetOpen = false;
try
{
var ispezione = SteupDataService.InspectionPageState.Ispezione;
await IspezioniService.DeleteIspezioneAsync(ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore);
await Js.InvokeVoidAsync("goBack");
}
catch (Exception e)
{
await Dialog.ShowError(e.Message);
}
}
public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged; public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged;
} }
+115 -21
View File
@@ -2,6 +2,7 @@
@using Microsoft.Extensions.Logging @using Microsoft.Extensions.Logging
@using SteUp.Shared.Components.Layout @using SteUp.Shared.Components.Layout
@using SteUp.Shared.Components.Layout.Overlay @using SteUp.Shared.Components.Layout.Overlay
@using SteUp.Shared.Components.Layout.Sheet
@using SteUp.Shared.Components.SingleElements @using SteUp.Shared.Components.SingleElements
@using SteUp.Shared.Components.SingleElements.Card @using SteUp.Shared.Components.SingleElements.Card
@using SteUp.Shared.Components.SingleElements.MessageBox @using SteUp.Shared.Components.SingleElements.MessageBox
@@ -12,18 +13,66 @@
@using SteUp.Shared.Core.Interface.IntegryApi @using SteUp.Shared.Core.Interface.IntegryApi
@using SteUp.Shared.Core.Interface.LocalDb @using SteUp.Shared.Core.Interface.LocalDb
@using SteUp.Shared.Core.Interface.System @using SteUp.Shared.Core.Interface.System
@using SteUp.Shared.Core.Messages.Ispezione
@using SteUp.Shared.Core.Messages.Scheda
@inject NewSchedaService NewScheda
@inject CompleteInspectionService CompleteInspection
@inject IIspezioniService IspezioniService @inject IIspezioniService IspezioniService
@inject IDialogService Dialog @inject IDialogService Dialog
@inject IIntegrySteupService IntegrySteupService @inject IIntegrySteupService IntegrySteupService
@inject IFileManager FileManager @inject IFileManager FileManager
@inject ILogger<IspezionePage> Logger @inject ILogger<IspezionePage> Logger
@implements IDisposable
<HeaderLayout Title="@Titolo" Subtitle="@Sottotitolo" Back="true"/> <HeaderLayout Title="@Titolo" Subtitle="@Sottotitolo" Back="true">
<Actions>
@* Su un'ispezione conclusa resta solo la consultazione: niente azioni. *@
@if (!IsCompleted)
{
@* Qui le azioni sono piu' d'una e non hanno lo stesso peso: lo
sheet le mostra con nome, icona e tono, invece di nasconderle
tutte dietro lo stesso glifo. *@
<button type="button" class="appbar__icon-action" @onclick="@(() => _actionsOpen = true)"
aria-label="Azioni sull'ispezione" title="Azioni">
<i class="ri-more-2-fill" aria-hidden="true"></i>
</button>
}
</Actions>
</HeaderLayout>
<ActionSheet @bind-Open="_actionsOpen" Title="Azioni ispezione" Subtitle="@Ispezione.CodMdep">
<div class="stack stack-xs">
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="NewScheda">
<span class="action-tile__icon action-tile__icon--primary">
<i class="ri-add-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Nuova scheda</span>
</button>
@if (!Ispezione.ActivityId.IsNullOrEmpty())
{
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="ConfirmCompleteInspection">
<span class="action-tile__icon action-tile__icon--success">
<i class="ri-check-double-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Concludi ispezione</span>
</button>
}
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
@onclick="ConfirmDeleteInspection">
<span class="action-tile__icon action-tile__icon--danger">
<i class="ri-delete-bin-line" aria-hidden="true"></i>
</span>
<span class="action-tile__label">Elimina ispezione</span>
</button>
@if (!NetworkService.IsNetworkAvailable())
{
<div class="feedback feedback--warning" role="status">
<i class="feedback__icon ri-wifi-off-line" aria-hidden="true"></i>
<span>Senza connessione queste azioni non sono disponibili. Il lavoro gia' fatto resta sul dispositivo.</span>
</div>
}
</div>
</ActionSheet>
<div class="app-scroll"> <div class="app-scroll">
<div class="page-width page-body stack stack-lg"> <div class="page-width page-body stack stack-lg">
@@ -92,8 +141,16 @@
@if (!IsCompleted) @if (!IsCompleted)
{ {
<p class="empty-state__text"> <p class="empty-state__text">
Tocca <b>+</b> nella barra in basso per creare la prima scheda di questa ispezione. Aggiungi la prima scheda di questa ispezione.
</p> </p>
<div class="empty-state__action">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Rounded.Add"
Disabled="@(!NetworkService.IsNetworkAvailable())"
OnClick="NewScheda">
Nuova scheda
</MudButton>
</div>
} }
</div> </div>
} }
@@ -149,7 +206,14 @@
</div> </div>
<SpinnerOverlay VisibleOverlay="VisibleOverlay" SuccessAnimation="_success" Message="@_progressMessage"/> <SpinnerOverlay VisibleOverlay="VisibleOverlay" SuccessAnimation="_success" Message="@_progressMessage"/>
<ConfirmMessageBox @ref="_exportRepartoBox" NoText="Annulla" YesText="Invia" YesColor="Color.Primary" <ConfirmMessageBox @ref="_completeBox" Title="Concludere l'ispezione?" NoText="Annulla"
YesText="Concludi" YesColor="Color.Primary"
Message="Una volta conclusa, l'ispezione non sarà più modificabile e non potrai aggiungere altre schede."/>
<ConfirmMessageBox @ref="_deleteBox" Title="Eliminare l'ispezione?" NoText="Annulla"
YesText="Elimina" YesColor="Color.Error"
Message="L'ispezione e tutte le sue schede verranno eliminate dal dispositivo. L'operazione non è reversibile."/>
<ConfirmMessageBox @ref="_exportRepartoBox" Title="Inviare le schede del reparto?" NoText="Annulla"
YesText="Invia" YesColor="Color.Primary"
Message="Le schede complete di questo reparto verranno inviate al server. Procedere?"/> Message="Le schede complete di questo reparto verranno inviate al server. Procedere?"/>
@code { @code {
@@ -162,6 +226,10 @@
private string? _progressMessage; private string? _progressMessage;
private ConfirmMessageBox _exportRepartoBox = null!; private ConfirmMessageBox _exportRepartoBox = null!;
private ConfirmMessageBox _completeBox = null!;
private ConfirmMessageBox _deleteBox = null!;
private bool _actionsOpen;
private Ispezione Ispezione => SteupDataService.InspectionPageState.Ispezione; private Ispezione Ispezione => SteupDataService.InspectionPageState.Ispezione;
private bool IsCompleted => Ispezione.Stato == StatusEnum.Completata; private bool IsCompleted => Ispezione.Stato == StatusEnum.Completata;
@@ -185,13 +253,7 @@
private string Sottotitolo => Ispezione.CodMdep; private string Sottotitolo => Ispezione.CodMdep;
protected override void OnInitialized() protected override void OnInitialized() => LoadSchede();
{
NewScheda.OnNewScheda += LoadSchede;
CompleteInspection.OnComplete += HandleCompleteInspection;
LoadSchede();
}
private void LoadSchede() private void LoadSchede()
{ {
@@ -208,6 +270,44 @@
}); });
} }
private async Task NewScheda()
{
_actionsOpen = false;
var modal = await Dialog.OpenFormScheda(Ispezione.CodMdep, Ispezione.Data, true);
if (modal is { Canceled: false }) LoadSchede();
}
private async Task ConfirmCompleteInspection()
{
_actionsOpen = false;
var confirmed = await _completeBox.ShowAsync();
if (confirmed is true) HandleCompleteInspection();
}
private async Task ConfirmDeleteInspection()
{
_actionsOpen = false;
// Prima non c'era conferma: un tocco cancellava l'ispezione e tutte le
// sue schede senza appello.
var confirmed = await _deleteBox.ShowAsync();
if (confirmed is not true) return;
try
{
var ispezione = Ispezione;
await IspezioniService.DeleteIspezioneAsync(ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore);
NavigationManager.NavigateTo("/ispezioni");
}
catch (Exception e)
{
OnError(e, e.Message);
}
}
private async void HandleCompleteInspection() private async void HandleCompleteInspection()
{ {
// Guardia anti doppio-invio: il completamento è irreversibile. // Guardia anti doppio-invio: il completamento è irreversibile.
@@ -496,10 +596,4 @@
); );
} }
void IDisposable.Dispose()
{
NewScheda.OnNewScheda -= LoadSchede;
CompleteInspection.OnComplete -= HandleCompleteInspection;
}
} }
@@ -5,8 +5,21 @@
@using SteUp.Shared.Core.Entities @using SteUp.Shared.Core.Entities
@using SteUp.Shared.Core.Interface.LocalDb @using SteUp.Shared.Core.Interface.LocalDb
@inject IIspezioniService IspezioniService @inject IIspezioniService IspezioniService
@inject IDialogService Dialog
<HeaderLayout Title="Ispezioni" Subtitle="@Subtitle"/> <HeaderLayout Title="Ispezioni" Subtitle="@Subtitle">
<Actions>
@* L'unica azione della schermata: porta dritta alla scelta del punto
vendita. Un passaggio intermedio per scegliere fra una cosa sola non
e' una scelta, e' un tocco in piu'. *@
<button type="button" class="appbar__action" @onclick="NewInspection"
disabled="@(!NetworkService.IsNetworkAvailable())"
title="@(NetworkService.IsNetworkAvailable() ? null : "Serve la connessione per aprire una nuova ispezione")">
<i class="ri-add-line" aria-hidden="true"></i>
<span>Nuova</span>
</button>
</Actions>
</HeaderLayout>
<div class="app-scroll"> <div class="app-scroll">
<div class="page-width page-body"> <div class="page-width page-body">
@@ -32,8 +45,16 @@
<i class="empty-state__icon ri-clipboard-line" aria-hidden="true"></i> <i class="empty-state__icon ri-clipboard-line" aria-hidden="true"></i>
<p class="empty-state__title">Nessuna ispezione</p> <p class="empty-state__title">Nessuna ispezione</p>
<p class="empty-state__text"> <p class="empty-state__text">
Tocca <b>+</b> nella barra in basso per aprire la prima ispezione su un punto vendita. Apri la prima ispezione scegliendo il punto vendita da controllare.
</p> </p>
<div class="empty-state__action">
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Rounded.Add"
Disabled="@(!NetworkService.IsNetworkAvailable())"
OnClick="NewInspection">
Nuova ispezione
</MudButton>
</div>
</div> </div>
} }
else else
@@ -65,6 +86,22 @@
StateHasChanged(); StateHasChanged();
} }
private async Task NewInspection()
{
if (await SteupDataService.CanOpenNewInspection())
{
var modal = await Dialog.OpenSelectShop();
// La scelta del punto vendita naviga da sola sull'ispezione; se
// l'utente torna indietro, la lista dev'essere quella aggiornata.
if (modal is { Canceled: true }) return;
}
else
{
await Dialog.ShowWarning(
"Per aprire una nuova ispezione devi prima concludere tutte le ispezioni aperte da piu' di 20 giorni.");
}
}
private void OnClickIspezione(Ispezione ispezione) private void OnClickIspezione(Ispezione ispezione)
{ {
SteupDataService.InspectionPageState.Ispezione = ispezione; SteupDataService.InspectionPageState.Ispezione = ispezione;
@@ -1,5 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace SteUp.Shared.Core.Messages.Ispezione;
public class CompleteInspectionMessage(object? value = null) : ValueChangedMessage<object?>(value);
@@ -1,13 +0,0 @@
using CommunityToolkit.Mvvm.Messaging;
namespace SteUp.Shared.Core.Messages.Ispezione;
public class CompleteInspectionService
{
public event Action? OnComplete;
public CompleteInspectionService(IMessenger messenger)
{
messenger.Register<CompleteInspectionMessage>(this, (_, _) => { OnComplete?.Invoke(); });
}
}
@@ -1,5 +0,0 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace SteUp.Shared.Core.Messages.Scheda;
public class NewSchedaMessage(object? value = null) : ValueChangedMessage<object?>(value);
@@ -1,13 +0,0 @@
using CommunityToolkit.Mvvm.Messaging;
namespace SteUp.Shared.Core.Messages.Scheda;
public class NewSchedaService
{
public event Action? OnNewScheda;
public NewSchedaService(IMessenger messenger)
{
messenger.Register<NewSchedaMessage>(this, (_, _) => { OnNewScheda?.Invoke(); });
}
}
+93 -31
View File
@@ -565,39 +565,18 @@
.navbar__label { .navbar__label {
font-size: var(--label-md-size); font-size: var(--label-md-size);
line-height: 1.2; line-height: 1.2;
font-weight: 600; font-weight: 500;
} }
/* Icona piena e pastiglia di selezione sulla voce attiva. */ /* Icona piena e pastiglia di selezione sulla voce attiva. */
.navbar__item.active { color: var(--primary-dark); } .navbar__item.active { color: var(--primary-dark); }
.navbar__item.active .navbar__label { font-weight: 600; }
.navbar__item.active .navbar__icon { background-color: var(--info-surface); } .navbar__item.active .navbar__icon { background-color: var(--info-surface); }
.navbar__item:active .navbar__icon { transform: scale(0.94); } .navbar__item:active .navbar__icon { transform: scale(0.94); }
/* Azione primaria della schermata, dentro la barra e a portata di pollice. */
.navbar__fab {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 44px;
margin: 12px var(--space-sm) 12px 0;
border: none;
border-radius: var(--radius-control);
background-color: var(--primary);
color: var(--on-primary);
font-size: 26px;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.navbar__fab:active { transform: scale(0.94); background-color: var(--primary-pressed); }
.navbar__fab:disabled { background-color: var(--divider); color: var(--ink-muted); }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.navbar__item:active .navbar__icon, .navbar__item:active .navbar__icon { transform: none; }
.navbar__fab:active { transform: none; }
} }
/* Da 720px la navigazione diventa una colonna laterale (rail). */ /* Da 720px la navigazione diventa una colonna laterale (rail). */
@@ -617,13 +596,6 @@
.navbar__item { flex: 0 0 auto; padding-block: var(--space-xs); } .navbar__item { flex: 0 0 auto; padding-block: var(--space-xs); }
.navbar__fab {
order: -1;
width: 56px;
height: 48px;
margin: var(--space-xs) auto var(--space-sm);
}
.app-shell { padding-left: calc(96px + var(--space-lg)); } .app-shell { padding-left: calc(96px + var(--space-lg)); }
.page-body { padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px)); } .page-body { padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px)); }
} }
@@ -1123,3 +1095,93 @@
.skeleton--line { height: 15px; width: 100%; } .skeleton--line { height: 15px; width: 100%; }
.skeleton--line-short { height: 15px; width: 40%; } .skeleton--line-short { height: 15px; width: 40%; }
.skeleton--badge { height: 24px; width: 110px; border-radius: var(--radius-pill); } .skeleton--badge { height: 24px; width: 110px; border-radius: var(--radius-pill); }
/* -----------------------------------------------------------------------------
Azioni della testata
L'azione primaria di una schermata sta qui, con il suo nome: dice cosa fa
prima di essere toccata, e resta la stessa cosa in ogni pagina. Il vecchio
"+" dentro la barra di navigazione cambiava significato da una schermata
all'altra e non lo dichiarava mai.
-------------------------------------------------------------------------- */
.appbar__action {
display: inline-flex;
align-items: center;
gap: var(--space-6);
min-height: 40px;
padding: 0 var(--space-14) 0 var(--space-sm);
border: none;
border-radius: var(--radius-pill);
background-color: var(--primary);
color: var(--on-primary);
font-family: inherit;
font-size: var(--label-lg-size);
font-weight: 600;
white-space: nowrap;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.appbar__action i { font-size: 20px; }
.appbar__action:active { transform: scale(0.96); background-color: var(--primary-pressed); }
.appbar__action:disabled { background-color: var(--divider); color: var(--ink-muted); }
/* Variante a sola icona, per le schermate dove le azioni sono piu' d'una e
vivono in un bottom sheet. */
.appbar__icon-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--touch-min);
height: var(--touch-min);
border: none;
border-radius: var(--radius-pill);
background: none;
color: var(--ink);
font-size: 22px;
transition: background-color var(--duration-fast) var(--ease-out);
}
.appbar__icon-action:active { background-color: var(--surface-muted); }
@media (prefers-reduced-motion: reduce) {
.appbar__action:active { transform: none; }
}
/* -----------------------------------------------------------------------------
Riga di scelta di un punto vendita
E' una card, ma si comporta da riga: testo a sinistra, chevron a destra.
-------------------------------------------------------------------------- */
.shop-card {
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
border: none;
font-family: inherit;
text-align: left;
}
.shop-card__text {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
}
.shop-card__chevron {
flex: 0 0 auto;
font-size: 22px;
color: var(--ink-muted);
}
/* L'indirizzo serve a distinguere due negozi con lo stesso nome, non a essere
letto per intero: due righe bastano e tengono l'elenco scorrevole. */
.shop-card .list-card__meta {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}