Widgets

Button

Pendahuluan

Nylo Website menyediakan kelas Button dengan delapan gaya button siap pakai. Setiap button dilengkapi dengan dukungan bawaan untuk:

  • Status loading async -- kembalikan Future dari onPressed dan button secara otomatis menampilkan indikator loading
  • Gaya animasi -- pilih dari efek clickable, bounce, pulse, squeeze, jelly, shine, ripple, morph, dan shake
  • Gaya splash -- tambahkan umpan balik sentuh ripple, highlight, glow, atau ink
  • Pengiriman formulir -- hubungkan button langsung ke instance NyFormData

Anda dapat menemukan definisi button aplikasi Anda di lib/resources/widgets/buttons/buttons.dart. File ini berisi kelas Button dengan method statis untuk setiap tipe button, memudahkan Anda untuk menyesuaikan nilai default proyek Anda.

Penggunaan Dasar

Gunakan kelas Button di mana saja dalam widget Anda. Berikut contoh sederhana di dalam sebuah halaman:

@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);
              },
            ),
          ],
        ),
      ),
    ),
  );
}

Setiap tipe button mengikuti pola yang sama -- berikan label text dan callback onPressed.

Tipe Button yang Tersedia

Semua button diakses melalui kelas Button menggunakan method statis.

Primary

Button terisi dengan bayangan, menggunakan warna primer tema Anda. Terbaik untuk elemen call-to-action utama.

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

Secondary

Button terisi dengan warna permukaan yang lebih lembut dan bayangan halus. Cocok untuk aksi sekunder di samping button primer.

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

Outlined

Button transparan dengan garis tepi. Berguna untuk aksi yang kurang menonjol atau button batal.

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

Anda dapat menyesuaikan warna garis tepi dan teks:

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

Text Only

Button minimal tanpa latar belakang atau garis tepi. Ideal untuk aksi inline atau tautan.

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

Anda dapat menyesuaikan warna teks:

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

Icon

Button terisi yang menampilkan ikon di samping teks. Ikon muncul sebelum teks secara default.

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

Anda dapat menyesuaikan warna latar belakang:

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

Gradient

Button dengan latar belakang gradien linear. Menggunakan warna primer dan tersier tema Anda secara default.

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

Anda dapat memberikan warna gradien kustom:

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

Rounded

Button berbentuk pil dengan sudut yang sepenuhnya membulat. Radius border secara default adalah setengah dari tinggi button.

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

Anda dapat menyesuaikan warna latar belakang dan radius border:

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

Transparency

Button bergaya kaca buram dengan efek blur latar belakang. Bekerja dengan baik saat ditempatkan di atas gambar atau latar belakang berwarna.

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

Anda dapat menyesuaikan warna teks:

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

Status Loading Async

Salah satu fitur paling kuat dari button Nylo Website adalah manajemen status loading otomatis. Ketika callback onPressed Anda mengembalikan Future, button akan secara otomatis menampilkan indikator loading dan menonaktifkan interaksi hingga operasi selesai.

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

Selama operasi async berjalan, button akan menampilkan efek skeleton loading (secara default). Setelah Future selesai, button kembali ke status normalnya.

Ini bekerja dengan operasi async apa pun -- panggilan API, penulisan database, upload file, atau apa pun yang mengembalikan 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();
  },
)

Tidak perlu mengelola variabel state isLoading, memanggil setState, atau membungkus apa pun dalam StatefulWidget -- Nylo Website menangani semuanya untuk Anda.

Cara Kerjanya

Ketika button mendeteksi bahwa onPressed mengembalikan Future, ia menggunakan mekanisme lockRelease untuk:

  1. Menampilkan indikator loading (dikendalikan oleh LoadingStyle)
  2. Menonaktifkan button untuk mencegah ketukan ganda
  3. Menunggu Future selesai
  4. Mengembalikan button ke status normalnya

Gaya Animasi

Button mendukung animasi tekan melalui ButtonAnimationStyle. Animasi ini memberikan umpan balik visual saat pengguna berinteraksi dengan button. Anda dapat mengatur gaya animasi saat menyesuaikan button Anda di lib/resources/widgets/buttons/buttons.dart.

Clickable

Efek tekan 3D bergaya Duolingo. Button bergerak ke bawah saat ditekan dan memantul kembali saat dilepas. Terbaik untuk aksi utama dan UX bergaya permainan.

animationStyle: ButtonAnimationStyle.clickable()

Sesuaikan efeknya:

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

Mengecilkan button saat ditekan dan memantul kembali saat dilepas. Terbaik untuk button tambah-ke-keranjang, suka, dan favorit.

animationStyle: ButtonAnimationStyle.bounce()

Sesuaikan efeknya:

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

Pulse

Denyut skala halus yang berkelanjutan saat button ditahan. Terbaik untuk aksi tekan-lama atau menarik perhatian.

animationStyle: ButtonAnimationStyle.pulse()

Sesuaikan efeknya:

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

Squeeze

Memampatkan button secara horizontal dan memperluasnya secara vertikal saat ditekan. Terbaik untuk UI yang menyenangkan dan interaktif.

animationStyle: ButtonAnimationStyle.squeeze()

Sesuaikan efeknya:

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

Efek deformasi elastis yang bergoyang. Terbaik untuk aplikasi yang menyenangkan, kasual, atau hiburan.

animationStyle: ButtonAnimationStyle.jelly()

Sesuaikan efeknya:

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

Shine

Sorotan mengkilap yang menyapu melintasi button saat ditekan. Terbaik untuk fitur premium atau CTA yang ingin Anda tonjolkan.

animationStyle: ButtonAnimationStyle.shine()

Sesuaikan efeknya:

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

Efek riak yang diperbesar yang meluas dari titik sentuh. Terbaik untuk penekanan Material Design.

animationStyle: ButtonAnimationStyle.ripple()

Sesuaikan efeknya:

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

Morph

Radius border button bertambah saat ditekan, menciptakan efek perubahan bentuk. Terbaik untuk umpan balik yang halus dan elegan.

animationStyle: ButtonAnimationStyle.morph()

Sesuaikan efeknya:

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

Shake

Animasi goyang horizontal. Terbaik untuk status error atau aksi tidak valid -- goyangkan button untuk memberi sinyal bahwa ada yang salah.

animationStyle: ButtonAnimationStyle.shake()

Sesuaikan efeknya:

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

Menonaktifkan Animasi

Untuk menggunakan button tanpa animasi:

animationStyle: ButtonAnimationStyle.none()

Mengubah Animasi Default

Untuk mengubah animasi default untuk tipe button, ubah file lib/resources/widgets/buttons/buttons.dart Anda:

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

Gaya Splash

Efek splash memberikan umpan balik sentuh visual pada button. Konfigurasikan melalui ButtonSplashStyle. Gaya splash dapat dikombinasikan dengan gaya animasi untuk umpan balik berlapis.

Gaya Splash yang Tersedia

Splash Factory Deskripsi
Ripple ButtonSplashStyle.ripple() Riak Material standar dari titik sentuh
Highlight ButtonSplashStyle.highlight() Sorotan halus tanpa animasi riak
Glow ButtonSplashStyle.glow() Cahaya lembut yang memancar dari titik sentuh
Ink ButtonSplashStyle.ink() Percikan tinta cepat, lebih cepat dan lebih responsif
None ButtonSplashStyle.none() Tanpa efek splash
Custom ButtonSplashStyle.custom() Kontrol penuh atas factory splash

Contoh

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

Anda dapat menyesuaikan warna splash dan opasitas:

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

Gaya Loading

Indikator loading yang ditampilkan selama operasi async dikendalikan oleh LoadingStyle. Anda dapat mengaturnya per tipe button di file buttons Anda.

Skeletonizer (Default)

Menampilkan efek shimmer skeleton pada button:

loadingStyle: LoadingStyle.skeletonizer()

Normal

Menampilkan widget loading (secara default loader aplikasi):

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

None

Menjaga button tetap terlihat tetapi menonaktifkan interaksi selama loading:

loadingStyle: LoadingStyle.none()

Pengiriman Formulir

Semua button mendukung parameter submitForm, yang menghubungkan button ke NyForm. Saat diketuk, button akan memvalidasi formulir dan memanggil handler sukses Anda dengan data formulir.

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

Parameter submitForm menerima record dengan dua nilai:

  1. Instance NyFormData (atau nama formulir sebagai String)
  2. Callback yang menerima data yang telah divalidasi

Secara default, showToastError bernilai true, yang menampilkan notifikasi toast saat validasi formulir gagal. Atur ke false untuk menangani error secara diam-diam:

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

Ketika callback submitForm mengembalikan Future, button akan secara otomatis menampilkan status loading hingga operasi async selesai.

Menyesuaikan Button

Semua default button didefinisikan di proyek Anda di lib/resources/widgets/buttons/buttons.dart. Setiap tipe button memiliki kelas widget yang sesuai di lib/resources/widgets/buttons/partials/.

Mengubah Gaya Default

Untuk mengubah tampilan default sebuah button, edit kelas 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(),
    );
  }
}

Menyesuaikan Widget Button

Untuk mengubah tampilan visual dari tipe button, edit widget yang sesuai di lib/resources/widgets/buttons/partials/. Misalnya, untuk mengubah radius border atau bayangan button primer:

// 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,
          ),
        ),
      ),
    );
  }
}

Membuat Tipe Button Baru

Untuk menambahkan tipe button baru:

  1. Buat file widget baru di lib/resources/widgets/buttons/partials/ yang meng-extend StatefulAppButton.
  2. Implementasikan method buildButton.
  3. Tambahkan method statis di kelas 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,
          ),
        ),
      ),
    );
  }
}

Kemudian daftarkan di kelas 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(),
    );
  }
}

Referensi Parameter

Parameter Umum (Semua Tipe Button)

Parameter Tipe Default Deskripsi
text String wajib Teks label button
onPressed VoidCallback? null Callback saat button diketuk. Kembalikan Future untuk status loading otomatis
submitForm (dynamic, Function(dynamic))? null Record pengiriman formulir (instance formulir, callback sukses)
onFailure Function(dynamic)? null Dipanggil saat validasi formulir gagal
showToastError bool true Tampilkan notifikasi toast saat error validasi formulir
width double? null Lebar button (default lebar penuh)

Parameter Khusus Tipe

Button.outlined

Parameter Tipe Default Deskripsi
borderColor Color? Warna outline tema Warna garis tepi
textColor Color? Warna primer tema Warna teks

Button.textOnly

Parameter Tipe Default Deskripsi
textColor Color? Warna primer tema Warna teks

Button.icon

Parameter Tipe Default Deskripsi
icon Widget wajib Widget ikon yang ditampilkan
color Color? Warna primer tema Warna latar belakang

Button.gradient

Parameter Tipe Default Deskripsi
gradientColors List<Color>? Warna primer dan tersier Titik warna gradien

Button.rounded

Parameter Tipe Default Deskripsi
backgroundColor Color? Warna primary container tema Warna latar belakang
borderRadius BorderRadius? Bentuk pil (tinggi / 2) Radius sudut

Button.transparency

Parameter Tipe Default Deskripsi
color Color? Adaptif tema Warna teks

Parameter ButtonAnimationStyle

Parameter Tipe Default Deskripsi
duration Duration Bervariasi per tipe Durasi animasi
curve Curve Bervariasi per tipe Kurva animasi
enableHapticFeedback bool Bervariasi per tipe Memicu umpan balik haptik saat ditekan
translateY double 4.0 Clickable: jarak tekan vertikal
shadowOffset double 4.0 Clickable: kedalaman bayangan
scaleMin double 0.92 Bounce: skala minimum saat ditekan
pulseScale double 1.05 Pulse: skala maksimum selama denyut
squeezeX double 0.95 Squeeze: kompresi horizontal
squeezeY double 1.05 Squeeze: ekspansi vertikal
jellyStrength double 0.15 Jelly: intensitas goyangan
shineColor Color Colors.white Shine: warna sorotan
shineWidth double 0.3 Shine: lebar pita kilau
rippleScale double 2.0 Ripple: skala ekspansi
morphRadius double 24.0 Morph: radius border target
shakeOffset double 8.0 Shake: perpindahan horizontal
shakeCount int 3 Shake: jumlah osilasi

Parameter ButtonSplashStyle

Parameter Tipe Default Deskripsi
splashColor Color? Warna surface tema Warna efek splash
highlightColor Color? Warna surface tema Warna efek highlight
splashOpacity double 0.12 Opasitas splash
highlightOpacity double 0.06 Opasitas highlight
borderRadius BorderRadius? null Radius clip splash