Widgets

Button

Introduzione

Nylo Website fornisce una classe Button con otto stili di button predefiniti. Ogni button include il supporto integrato per:

  • Stati di caricamento async -- restituisci un Future da onPressed e il button mostra automaticamente un indicatore di caricamento
  • Stili di animazione -- scegli tra effetti clickable, bounce, pulse, squeeze, jelly, shine, ripple, morph e shake
  • Stili splash -- aggiungi feedback tattile ripple, highlight, glow o ink
  • Invio moduli -- collega un button direttamente a un'istanza NyFormData

Puoi trovare le definizioni dei button della tua app in lib/resources/widgets/buttons/buttons.dart. Questo file contiene una classe Button con metodi statici per ogni tipo di button, rendendo semplice personalizzare i valori predefiniti per il tuo progetto.

Utilizzo Base

Usa la classe Button ovunque nei tuoi widget. Ecco un esempio semplice all'interno di una pagina:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Button.primary(
              text: "Sign Up",
              onPressed: () {
                routeTo(SignUpPage.path);
              },
            ),

            SizedBox(height: 12),

            Button.secondary(
              text: "Learn More",
              onPressed: () {
                routeTo(AboutPage.path);
              },
            ),

            SizedBox(height: 12),

            Button.outlined(
              text: "Cancel",
              onPressed: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    ),
  );
}

Ogni tipo di button segue lo stesso schema -- passa un'etichetta text e un callback onPressed.

Tipi di Button Disponibili

Tutti i button sono accessibili tramite la classe Button usando metodi statici.

Primary

Un button riempito con ombra, che utilizza il colore primario del tuo tema. Ideale per gli elementi call-to-action principali.

Button.primary(
  text: "Sign Up",
  onPressed: () {
    // Handle press
  },
)

Secondary

Un button riempito con un colore di superficie piu morbido e un'ombra sottile. Adatto per azioni secondarie accanto a un button primario.

Button.secondary(
  text: "Learn More",
  onPressed: () {
    // Handle press
  },
)

Outlined

Un button trasparente con un bordo. Utile per azioni meno prominenti o button di annullamento.

Button.outlined(
  text: "Cancel",
  onPressed: () {
    // Handle press
  },
)

Puoi personalizzare i colori del bordo e del testo:

Button.outlined(
  text: "Custom Outline",
  borderColor: Colors.red,
  textColor: Colors.red,
  onPressed: () {},
)

Text Only

Un button minimale senza sfondo o bordo. Ideale per azioni inline o link.

Button.textOnly(
  text: "Skip",
  onPressed: () {
    // Handle press
  },
)

Puoi personalizzare il colore del testo:

Button.textOnly(
  text: "View Details",
  textColor: Colors.blue,
  onPressed: () {},
)

Icon

Un button riempito che visualizza un'icona accanto al testo. L'icona appare prima del testo per impostazione predefinita.

Button.icon(
  text: "Add to Cart",
  icon: Icon(Icons.shopping_cart),
  onPressed: () {
    // Handle press
  },
)

Puoi personalizzare il colore di sfondo:

Button.icon(
  text: "Download",
  icon: Icon(Icons.download),
  color: Colors.green,
  onPressed: () {},
)

Gradient

Un button con uno sfondo a gradiente lineare. Utilizza i colori primario e terziario del tuo tema per impostazione predefinita.

Button.gradient(
  text: "Get Started",
  onPressed: () {
    // Handle press
  },
)

Puoi fornire colori di gradiente personalizzati:

Button.gradient(
  text: "Premium",
  gradientColors: [Colors.purple, Colors.pink],
  onPressed: () {},
)

Rounded

Un button a forma di pillola con angoli completamente arrotondati. Il raggio del bordo e per impostazione predefinita la meta dell'altezza del button.

Button.rounded(
  text: "Continue",
  onPressed: () {
    // Handle press
  },
)

Puoi personalizzare il colore di sfondo e il raggio del bordo:

Button.rounded(
  text: "Apply",
  backgroundColor: Colors.teal,
  borderRadius: BorderRadius.circular(20),
  onPressed: () {},
)

Transparency

Un button in stile vetro smerigliato con effetto di sfocatura dello sfondo. Funziona bene quando posizionato sopra immagini o sfondi colorati.

Button.transparency(
  text: "Explore",
  onPressed: () {
    // Handle press
  },
)

Puoi personalizzare il colore del testo:

Button.transparency(
  text: "View More",
  color: Colors.white,
  onPressed: () {},
)

Stato di Caricamento Async

Una delle funzionalita piu potenti dei button Nylo Website e la gestione automatica dello stato di caricamento. Quando il tuo callback onPressed restituisce un Future, il button mostrera automaticamente un indicatore di caricamento e disabilitera l'interazione fino al completamento dell'operazione.

Button.primary(
  text: "Submit",
  onPressed: () async {
    await sleep(3); // Simulates a 3 second async task
  },
)

Durante l'operazione async, il button mostrera un effetto di caricamento skeleton (per impostazione predefinita). Una volta completato il Future, il button torna al suo stato normale.

Funziona con qualsiasi operazione async -- chiamate API, scritture su database, upload di file o qualsiasi cosa che restituisca un Future:

Button.primary(
  text: "Save Profile",
  onPressed: () async {
    await api<ApiService>((request) =>
      request.updateProfile(name: "John", email: "john@example.com")
    );
    showToastSuccess(description: "Profile saved!");
  },
)
Button.secondary(
  text: "Sync Data",
  onPressed: () async {
    await fetchAndStoreData();
    await clearOldCache();
  },
)

Non c'e bisogno di gestire variabili di stato isLoading, chiamare setState o avvolgere qualsiasi cosa in un StatefulWidget -- Nylo Website gestisce tutto per te.

Come Funziona

Quando il button rileva che onPressed restituisce un Future, utilizza il meccanismo lockRelease per:

  1. Mostrare un indicatore di caricamento (controllato da LoadingStyle)
  2. Disabilitare il button per prevenire tap duplicati
  3. Attendere il completamento del Future
  4. Ripristinare il button al suo stato normale

Stili di Animazione

I button supportano animazioni alla pressione tramite ButtonAnimationStyle. Queste animazioni forniscono feedback visivo quando un utente interagisce con un button. Puoi impostare lo stile di animazione quando personalizzi i tuoi button in lib/resources/widgets/buttons/buttons.dart.

Clickable

Un effetto di pressione 3D in stile Duolingo. Il button si sposta verso il basso alla pressione e rimbalza al rilascio. Ideale per azioni principali e UX in stile gioco.

animationStyle: ButtonAnimationStyle.clickable()

Regola l'effetto:

ButtonAnimationStyle.clickable(
  translateY: 6.0,        // How far the button moves down (default: 4.0)
  shadowOffset: 6.0,      // Shadow depth (default: 4.0)
  duration: Duration(milliseconds: 100),
  enableHapticFeedback: true,
)

Bounce

Riduce la scala del button alla pressione e rimbalza al rilascio. Ideale per button aggiungi-al-carrello, mi piace e preferiti.

animationStyle: ButtonAnimationStyle.bounce()

Regola l'effetto:

ButtonAnimationStyle.bounce(
  scaleMin: 0.90,         // Minimum scale on press (default: 0.92)
  duration: Duration(milliseconds: 150),
  curve: Curves.easeOutBack,
  enableHapticFeedback: true,
)

Pulse

Un sottile impulso di scala continuo mentre il button e tenuto premuto. Ideale per azioni a pressione prolungata o per attirare l'attenzione.

animationStyle: ButtonAnimationStyle.pulse()

Regola l'effetto:

ButtonAnimationStyle.pulse(
  pulseScale: 1.08,       // Max scale during pulse (default: 1.05)
  duration: Duration(milliseconds: 800),
  curve: Curves.easeInOut,
)

Squeeze

Comprime il button orizzontalmente e lo espande verticalmente alla pressione. Ideale per UI giocose e interattive.

animationStyle: ButtonAnimationStyle.squeeze()

Regola l'effetto:

ButtonAnimationStyle.squeeze(
  squeezeX: 0.93,         // Horizontal scale (default: 0.95)
  squeezeY: 1.07,         // Vertical scale (default: 1.05)
  duration: Duration(milliseconds: 120),
  enableHapticFeedback: true,
)

Jelly

Un effetto di deformazione elastica oscillante. Ideale per app divertenti, casual o di intrattenimento.

animationStyle: ButtonAnimationStyle.jelly()

Regola l'effetto:

ButtonAnimationStyle.jelly(
  jellyStrength: 0.2,     // Wobble intensity (default: 0.15)
  duration: Duration(milliseconds: 300),
  curve: Curves.elasticOut,
  enableHapticFeedback: true,
)

Shine

Un riflesso lucido che scorre attraverso il button alla pressione. Ideale per funzionalita premium o CTA a cui vuoi attirare l'attenzione.

animationStyle: ButtonAnimationStyle.shine()

Regola l'effetto:

ButtonAnimationStyle.shine(
  shineColor: Colors.white,  // Color of the shine streak (default: white)
  shineWidth: 0.4,           // Width of the shine band (default: 0.3)
  duration: Duration(milliseconds: 600),
)

Ripple

Un effetto onda potenziato che si espande dal punto di tocco. Ideale per l'enfasi Material Design.

animationStyle: ButtonAnimationStyle.ripple()

Regola l'effetto:

ButtonAnimationStyle.ripple(
  rippleScale: 2.5,       // How far the ripple expands (default: 2.0)
  duration: Duration(milliseconds: 400),
  curve: Curves.easeOut,
  enableHapticFeedback: true,
)

Morph

Il raggio del bordo del button aumenta alla pressione, creando un effetto di trasformazione della forma. Ideale per un feedback sottile ed elegante.

animationStyle: ButtonAnimationStyle.morph()

Regola l'effetto:

ButtonAnimationStyle.morph(
  morphRadius: 30.0,      // Target border radius on press (default: 24.0)
  duration: Duration(milliseconds: 150),
  curve: Curves.easeInOut,
)

Shake

Un'animazione di scuotimento orizzontale. Ideale per stati di errore o azioni non valide -- scuoti il button per segnalare che qualcosa e andato storto.

animationStyle: ButtonAnimationStyle.shake()

Regola l'effetto:

ButtonAnimationStyle.shake(
  shakeOffset: 10.0,      // Horizontal displacement (default: 8.0)
  shakeCount: 4,          // Number of shakes (default: 3)
  duration: Duration(milliseconds: 400),
  enableHapticFeedback: true,
)

Disabilitare le Animazioni

Per usare un button senza animazione:

animationStyle: ButtonAnimationStyle.none()

Cambiare l'Animazione Predefinita

Per cambiare l'animazione predefinita per un tipo di button, modifica il file lib/resources/widgets/buttons/buttons.dart:

class Button {
  static Widget primary({
    required String text,
    VoidCallback? onPressed,
    ...
  }) {
    return PrimaryButton(
      text: text,
      onPressed: onPressed,
      animationStyle: ButtonAnimationStyle.bounce(), // Change the default
    );
  }
}

Stili Splash

Gli effetti splash forniscono feedback tattile visivo sui button. Configurali tramite ButtonSplashStyle. Gli stili splash possono essere combinati con gli stili di animazione per un feedback stratificato.

Stili Splash Disponibili

Splash Factory Descrizione
Ripple ButtonSplashStyle.ripple() Onda Material standard dal punto di tocco
Highlight ButtonSplashStyle.highlight() Evidenziazione sottile senza animazione onda
Glow ButtonSplashStyle.glow() Bagliore morbido che si irradia dal punto di tocco
Ink ButtonSplashStyle.ink() Splash di inchiostro rapido, piu veloce e reattivo
None ButtonSplashStyle.none() Nessun effetto splash
Custom ButtonSplashStyle.custom() Controllo completo sulla factory splash

Esempio

class Button {
  static Widget outlined({
    required String text,
    VoidCallback? onPressed,
    ...
  }) {
    return OutlinedButton(
      text: text,
      onPressed: onPressed,
      splashStyle: ButtonSplashStyle.ripple(),
      animationStyle: ButtonAnimationStyle.clickable(),
    );
  }
}

Puoi personalizzare i colori splash e l'opacita:

ButtonSplashStyle.ripple(
  splashColor: Colors.blue,
  highlightColor: Colors.blue,
  splashOpacity: 0.2,
  highlightOpacity: 0.1,
)

Stili di Caricamento

L'indicatore di caricamento mostrato durante le operazioni async e controllato da LoadingStyle. Puoi impostarlo per tipo di button nel tuo file buttons.

Skeletonizer (Predefinito)

Visualizza un effetto shimmer skeleton sul button:

loadingStyle: LoadingStyle.skeletonizer()

Normal

Mostra un widget di caricamento (per impostazione predefinita il loader dell'app):

loadingStyle: LoadingStyle.normal(
  child: Text("Please wait..."),
)

None

Mantiene il button visibile ma disabilita l'interazione durante il caricamento:

loadingStyle: LoadingStyle.none()

Invio Moduli

Tutti i button supportano il parametro submitForm, che collega il button a un NyForm. Quando toccato, il button validera il modulo e chiamera il tuo handler di successo con i dati del modulo.

Button.primary(
  text: "Submit",
  submitForm: (LoginForm(), (data) {
    // data contains the validated form fields
    print(data);
  }),
  onFailure: (error) {
    // Handle validation errors
    print(error);
  },
)

Il parametro submitForm accetta un record con due valori:

  1. Un'istanza NyFormData (o nome del modulo come String)
  2. Un callback che riceve i dati validati

Per impostazione predefinita, showToastError e true, che mostra una notifica toast quando la validazione del modulo fallisce. Impostalo a false per gestire gli errori silenziosamente:

Button.primary(
  text: "Login",
  submitForm: (LoginForm(), (data) async {
    await api<AuthApiService>((request) => request.login(data));
  }),
  showToastError: false,
  onFailure: (error) {
    // Custom error handling
  },
)

Quando il callback submitForm restituisce un Future, il button mostrera automaticamente uno stato di caricamento fino al completamento dell'operazione async.

Personalizzare i Button

Tutti i valori predefiniti dei button sono definiti nel tuo progetto in lib/resources/widgets/buttons/buttons.dart. Ogni tipo di button ha una classe widget corrispondente in lib/resources/widgets/buttons/partials/.

Modificare gli Stili Predefiniti

Per modificare l'aspetto predefinito di un button, modifica la classe Button:

class Button {
  static Widget primary({
    required String text,
    VoidCallback? onPressed,
    (dynamic, Function(dynamic data))? submitForm,
    Function(dynamic error)? onFailure,
    bool showToastError = true,
    double? width,
  }) {
    return PrimaryButton(
      text: text,
      onPressed: onPressed,
      submitForm: submitForm,
      onFailure: onFailure,
      showToastError: showToastError,
      loadingStyle: LoadingStyle.skeletonizer(),
      width: width,
      height: 52.0,
      animationStyle: ButtonAnimationStyle.bounce(),
      splashStyle: ButtonSplashStyle.glow(),
    );
  }
}

Personalizzare un Widget Button

Per cambiare l'aspetto visivo di un tipo di button, modifica il widget corrispondente in lib/resources/widgets/buttons/partials/. Ad esempio, per cambiare il raggio del bordo o l'ombra del button primario:

// lib/resources/widgets/buttons/partials/primary_button_widget.dart

class PrimaryButton extends StatefulAppButton {
  ...

  @override
  Widget buildButton(BuildContext context) {
    final theme = Theme.of(context);
    final bgColor = backgroundColor ?? theme.colorScheme.primary;
    final fgColor = contentColor ?? theme.colorScheme.onPrimary;

    return Container(
      width: width ?? double.infinity,
      height: height,
      decoration: BoxDecoration(
        color: bgColor,
        borderRadius: BorderRadius.circular(8), // Change the radius
      ),
      child: Center(
        child: Text(
          text,
          style: TextStyle(
            color: fgColor,
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    );
  }
}

Creare un Nuovo Tipo di Button

Per aggiungere un nuovo tipo di button:

  1. Crea un nuovo file widget in lib/resources/widgets/buttons/partials/ che estende StatefulAppButton.
  2. Implementa il metodo buildButton.
  3. Aggiungi un metodo statico nella classe Button.
// lib/resources/widgets/buttons/partials/danger_button_widget.dart

class DangerButton extends StatefulAppButton {
  DangerButton({
    required super.text,
    super.onPressed,
    super.submitForm,
    super.onFailure,
    super.showToastError,
    super.loadingStyle,
    super.width,
    super.height,
    super.animationStyle,
    super.splashStyle,
  });

  @override
  Widget buildButton(BuildContext context) {
    return Container(
      width: width ?? double.infinity,
      height: height,
      decoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.circular(14),
      ),
      child: Center(
        child: Text(
          text,
          style: TextStyle(
            color: Colors.white,
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    );
  }
}

Poi registralo nella classe Button:

class Button {
  ...

  static Widget danger({
    required String text,
    VoidCallback? onPressed,
    (dynamic, Function(dynamic data))? submitForm,
    Function(dynamic error)? onFailure,
    bool showToastError = true,
    double? width,
  }) {
    return DangerButton(
      text: text,
      onPressed: onPressed,
      submitForm: submitForm,
      onFailure: onFailure,
      showToastError: showToastError,
      loadingStyle: LoadingStyle.skeletonizer(),
      width: width,
      height: 52.0,
      animationStyle: ButtonAnimationStyle.shake(),
    );
  }
}

Riferimento Parametri

Parametri Comuni (Tutti i Tipi di Button)

Parametro Tipo Predefinito Descrizione
text String obbligatorio Il testo dell'etichetta del button
onPressed VoidCallback? null Callback quando il button viene toccato. Restituisci un Future per lo stato di caricamento automatico
submitForm (dynamic, Function(dynamic))? null Record di invio modulo (istanza modulo, callback successo)
onFailure Function(dynamic)? null Chiamato quando la validazione del modulo fallisce
showToastError bool true Mostra notifica toast in caso di errore di validazione del modulo
width double? null Larghezza del button (predefinito larghezza piena)

Parametri Specifici per Tipo

Button.outlined

Parametro Tipo Predefinito Descrizione
borderColor Color? Colore outline del tema Colore del bordo
textColor Color? Colore primario del tema Colore del testo

Button.textOnly

Parametro Tipo Predefinito Descrizione
textColor Color? Colore primario del tema Colore del testo

Button.icon

Parametro Tipo Predefinito Descrizione
icon Widget obbligatorio Il widget icona da visualizzare
color Color? Colore primario del tema Colore di sfondo

Button.gradient

Parametro Tipo Predefinito Descrizione
gradientColors List<Color>? Colori primario e terziario Punti di colore del gradiente

Button.rounded

Parametro Tipo Predefinito Descrizione
backgroundColor Color? Colore primary container del tema Colore di sfondo
borderRadius BorderRadius? Forma pillola (altezza / 2) Raggio degli angoli

Button.transparency

Parametro Tipo Predefinito Descrizione
color Color? Adattivo al tema Colore del testo

Parametri ButtonAnimationStyle

Parametro Tipo Predefinito Descrizione
duration Duration Varia per tipo Durata dell'animazione
curve Curve Varia per tipo Curva dell'animazione
enableHapticFeedback bool Varia per tipo Attiva feedback aptico alla pressione
translateY double 4.0 Clickable: distanza di pressione verticale
shadowOffset double 4.0 Clickable: profondita dell'ombra
scaleMin double 0.92 Bounce: scala minima alla pressione
pulseScale double 1.05 Pulse: scala massima durante il pulse
squeezeX double 0.95 Squeeze: compressione orizzontale
squeezeY double 1.05 Squeeze: espansione verticale
jellyStrength double 0.15 Jelly: intensita dell'oscillazione
shineColor Color Colors.white Shine: colore del riflesso
shineWidth double 0.3 Shine: larghezza della banda di lucentezza
rippleScale double 2.0 Ripple: scala di espansione
morphRadius double 24.0 Morph: raggio del bordo target
shakeOffset double 8.0 Shake: spostamento orizzontale
shakeCount int 3 Shake: numero di oscillazioni

Parametri ButtonSplashStyle

Parametro Tipo Predefinito Descrizione
splashColor Color? Colore surface del tema Colore dell'effetto splash
highlightColor Color? Colore surface del tema Colore dell'effetto highlight
splashOpacity double 0.12 Opacita dello splash
highlightOpacity double 0.06 Opacita dell'highlight
borderRadius BorderRadius? null Raggio di clip dello splash