14 KiB
14 KiB
UI/UX Design Skill — Dutra Store (POS Grosir Flutter)
Gunakan skill ini sebagai acuan setiap kali mengubah, menambah, atau meninjau tampilan aplikasi POS grosir berbasis Flutter ini. Jika ada pertentangan, prioritaskan yang tercantum di sini.
1. Tujuan Utama
- Transaksi cepat tanpa hambatan visual.
- Tidak ada overflow/keluar layar di ukuran HP kecil.
- Status stok, harga, dan aksi utama terlihat jelas dalam 1 scan.
- Maintain konsistensi visual antar screen.
2. Design System
2.1 Tema
- Theme gelap (dark) sebagai basis utama.
- Warna aksen: amber (
Colors.amber). - Radius default: 18 untuk kartu, 12 untuk input/dialog, 8 untuk chip/tag.
- Font: gunakan
Theme.of(context).textThemeagar sesuai pengaturan sistem.
2.2 Warna Semantik & Token
kAccent— amber untuk aksen dan highlight.kSuccess— hijau untuk status lunas/berhasil.kDanger— merah untuk pembatalan/error/stok habis.kWarning— amber/oranye untuk peringatan stok rendah, bayar nanti.kMuted— abu-abu untuk teks sekunder/timestamp.kSurface2— background chip yang tidak aktif.kBorder— border untuk chip dan outline.kText— warna teks utama (default di theme gelap).
2.3 Typography Scale
Gunakan scale berikut, jangan hardcode font size kecuali specified:
- Display / H1: 28px, bold, untuk heading besar
- H2: 22px, bold, untuk section title
- H3: 18px, semi-bold, untuk card title / dialog title
- Body large: 16px, regular, untuk teks isi utama
- Body: 14px, regular, untuk teks body standar
- Body small: 13px, regular, untuk helper/subtitle
- Caption: 12px, regular, untuk timestamp/label kecil
- Overline: 10px, bold, uppercase tracking, untuk badge/status kecil
- Monospace: untuk angka/harga agar stabil
Semua angka harga/uang wajib menggunakan formatRupiah().
2.4 Spacing Scale
Pakai multiples of 4 untuk spacing:
- 4, 8, 12, 16, 20, 24, 32, 48
- Padding screen/list:
EdgeInsets.all(12) - Padding form/dialog:
EdgeInsets.all(16) - Gap antar field:
SizedBox(height: 10)atauSizedBox(height: 12) - Gap antar section:
SizedBox(height: 14)atauSizedBox(height: 20) - Jangan gunakan nilai spacing yang tidak kelipatan 4 kecuali ada alasan khusus
2.5 Elevation & Surface
- Card:
Cardwidget dengan default elevation, atauContainerdengancolor: kSurface2jika butuh kontrol penuh. - Active/pressed state: gunakan
InkWell/InkResponsedengan splash color aksen transparan. - Bottom sheet:
color: kSurface, border topkBorder, radius top 24. - Dialog:
backgroundColor: kSurface, content padding dariAlertDialogdefault atau override sesuai kebutuhan.
2.6 Iconography
- Ukuran icon default: 20–24dp untuk list tile, 18dp untuk button, 28dp untuk dialog icon.
- Gunakan outlined variant untuk icon di dalam input/field (prefixIcon).
- Gunakan filled/rounded untuk icon utama dan CTA.
- Jangan campur gaya icon dalam satu screen.
2.7 Animation Standard
- Micro-interaction (button press, card tap): duration 150–200ms, curve
Curves.easeInOut - Sheet expand/collapse: duration 300ms, curve
Curves.fastOutSlowIn - Dialog enter/exit: duration 200ms, curve
Curves.easeInOut - Spinner:
CircularProgressIndicatordenganstrokeWidth: 2, duration default - Pulse animation (cart badge): duration 180ms, scale 1.0 → 0.9 → 1.0
- SnackBar: duration default 3 detik, kecuali butuh lebih pendek/lama
- Jangan animasikan opacity/color untuk status penting (sukses/gagal) — gunakan warna solid agar cepat terdeteksi
2.8 Haptic & Feedback
- Aksi signifikan yang cepat: tambah item ke keranjang, print struk, scan barcode →
HapticFeedback.lightImpact(). - Aksi destruktif atau butuh konfirmasi: hapus item/batal transaksi →
HapticFeedback.mediumImpact(). - Sukses transaksi/tampilkan feedback →lanjut ke
SnackBarhijau (kSuccess), jangan pakai dialog sukses untuk hal yang cepat. - Jangan paksa haptic di setiap tap; hanya untuk aksi yang jelas berdampak.
2.9 Loading State
- Inline di button (recommended untuk semua operasi > 500ms):
- Spinner:
SizedBox(height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF06210F)))untuk light button text - Spinner:
SizedBox(height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2, color: kAccent))untuk dark button text - Ganti label menjadi
Mencetak.../Memproses.../Menyimpan... - Disable button (
onPressed: submitting ? null : ())
- Spinner:
- Full-screen: gunakan
CircularProgressIndicator(color: kAccent)diCenter. - Inline di list/sheet: gunakan
LinearProgressIndicatorhanya untuk determinate progress yang jelas. - Skeleton: untuk list yang loading, pakai
ListTiledenganshimmer effectatau placeholder abu-abu. - Jangan biarkan user bisa double-tap aksi yang sudah loading.
2.10 Empty State
- Jangan tampilkan teks kosong tanpa visual indicator.
- Minimal: icon 48dp + teks. Contoh:
Icon(Icons.shopping_cart_outlined, size: 48, color: kMuted)+ teks "Keranjang kosong". - Center alignment,
ColumndenganMainAxisSize.min. - Jika ada aksi yang bisa dilakukan (misal tambah produk), tampilkan
FilledButtonsebagai helper. - Konsisten pakai ikon
outlinedvariant untuk empty state.
2.11 Error State
- Error dari form validation: tampilkan inline di bawah field, warna
kDanger, font 13,maxLines: 2. - Error dari operasi database/API: tampilkan
SnackBarmerah (kDanger) dengan durasi 3 detik. - Jangan hanya pakai warna tanpa label. Tambahkan ikon/prefix jika perlu.
- Jika field error, pastikan
TextFieldtetap bisa di-focus dan di-scroll. - Untuk error yang bisa diperbaiki (misal form), jangan matikan form.
2.12 Dialog Button Layout
- Mobile (< 600dp): tombol dialog harus full-width stacked vertikal dengan
SizedBox(height: 8)antar tombol. - Tablet (>= 600dp): bisa pakai
Rowhorizontal untuk 2 tombol, denganExpanded. - Urutan: Batal di atas/kiri, Konfirmasi di bawah/kanan.
- Tombol konfirmasi destruktif:
FilledButtondenganbackgroundColor: kDanger. - Tombol konfirmasi positif:
FilledButtondenganbackgroundColor: kAccentataukSuccess. - Gunakan
StatefulBuilderjika dialog butuh loading state atau update UI setelah aksi.
2.13 Role-Based Visibility
- Fitur yang tidak diizinkan untuk role tertentu harus disembunyikan sepenuhnya, bukan cuma di-disable.
- Admin: lihat semua transaksi + filter tanggal + aksi batalkan transaksi + pengaturan toko + kelola akun kasir + harga pokok/laba.
- Kasir: lihat transaksi hari ini, tidak ada aksi batalkan, tidak ada filter tanggal, tidak ada pengaturan toko, tidak ada harga pokok/laba.
- Guest: tidak diizinkan akses.
2.14 Bottom Sheet
- Keranjang harus berupa bottom sheet, bukan panel tetap.
- Sheet harus bisa di-drag (
DraggableScrollableSheetataushowModalBottomSheetdenganisScrollControlled: true). - Konten sheet harus selalu scrollable (
SingleChildScrollView), tanpaExpandeddi dalamnya, agar footer tidak pernah terhalangi keyboard. - Untuk menutup sheet dari aksi di dalamnya, gunakan
Navigator.pop(sheetContext)bukanparentContext. - Tambah handle bar (container 40x4 dengan radius 4, warna
kBorder) di atas sheet untuk affordance drag.
2.15 Dialog
- Semua dialog input yang panjang harus dibungkus
SingleChildScrollViewatauSingleChildScrollViewdi dalamAlertDialogagar bisa di-scroll saat keyboard muncul. - Jangan pakai
Expandeddi dalam dialog yang berpotensi overflow. - Dialog dengan form:
contentPadding: EdgeInsets.fromLTRB(16, 16, 16, 8),actionsPadding: EdgeInsets.fromLTRB(16, 0, 16, 16). - Dialog dengan list items:
contentPadding: EdgeInsets.all(16). - Jangan buat dialog terlalu tinggi; batasi dengan
ConstrainedBox+SingleChildScrollViewjika konten bisa panjang.
2.16 SnackBar
- Gunakan
ScaffoldMessenger.of(context).showSnackBar()untuk feedback singkat. - Durasi default 3 detik untuk info, 5 detik untuk error penting.
- Posisikan di atas bottom navigation jika ada, atau default bottom.
- Jangan tampilkan SnackBar di atas dialog yang masih terbuka — tunggu dialog ditutup dulu.
2.17 Status & Badge
- Gunakan badge untuk status dengan warna sesuai:
- Lunas / Berhasil: badge hijau (
kSuccess) dengan label teks - Belum Lunas / Bayar Nanti: badge amber (
kWarning) dengan label teks - Dibatalkan: badge merah (
kDanger) + teksDIBATALKAN - Stok Habis: badge merah + teks "Habis"
- Stok Rendah: badge oranye + teks "Stok rendah (X/Y)"
- Manual: badge amber dengan label "MANUAL" (caps, tracking +0.5)
- Lunas / Berhasil: badge hijau (
- Badge style:
Containerdenganpadding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),borderRadius: BorderRadius.circular(8),color: warna.withValues(alpha: 0.15). - Untuk badge kecil (10–12px), gunakan
fontWeight: FontWeight.w800agar tetap terbaca.
2.18 List & Card
- Gunakan
ListView.separateddenganitemCountyang sesuai untuk performa. - Separator:
Divider(height: 1, thickness: 1, color: kBorder)atauSizedBox(height: 8/12)untuk card list. - Card list:
Cardwidget denganmargin: EdgeInsets.symmetric(horizontal: 12, vertical: 4)atauEdgeInsets.all(12). - Jangan hardcode tinggi list item; gunakan
Column+MainAxisSize.min. - Teks panjang:
maxLines: 2,overflow: TextOverflow.ellipsisuntuk nama produk.
2.19 Grid Produk
- Default: 2 kolom di layar HP (
crossAxisCount: 2). - Tablet (lebar > 600dp): naik jadi 3 kolom.
- Jangan pakai
childAspectRatiotetap yang bikin kartu terlalu tinggi/pendek. GunakanAspectRatio(1.1)atau biarkanGridViewyang menentukan tinggi. - Setiap kartu produk harus menampilkan: inisial nama (avatar circle amber), nama produk, harga, stok/badge stok.
2.20 Auto-Scroll & Keyboard
- Setiap form di dalam bottom sheet atau dialog harus meng-handle keyboard muncul/tutup.
- Gunakan
MediaQuery.of(context).viewInsets.bottomuntuk padding bawah dinamis padaSingleChildScrollView. - Untuk bottom sheet form yang tinggi dan perlu auto-expand:
- Pakai
DraggableScrollableController. - Buat
FocusNodeuntuk setiapTextField. - Tambah listener ke setiap focus node: jika
hasFocus, animasikan sheet kemaxChildSize(misal0.92), lalu panggilScrollable.ensureVisible()agar field aktif terlihat. - Durasi animasi sheet:
300ms, scroll:250ms, alignment:0.0.
- Pakai
- Jangan andal
Scaffold.resizeToAvoidBottomInsetsaja untuk bottom sheet form kompleks; manual padding lebih stabil untuk kasus form panjang.
2.21 Responsif
- Mobile (< 600dp): 2 kolom grid, tombol stacked vertikal di dialog.
- Tablet (>= 600dp): 3–4 kolom grid, tombol bisa horizontal di dialog.
- Gunakan
LayoutBuilderatauMediaQueryuntuk menentukan layout berdasarkan lebar screen. - Jangan gunakan hardcoded width untuk widget utama; gunakan
Expanded,Flexible, atauMediaQuery.
2.22 Aksesibilitas
- Minimal tap target: 44x44dp untuk semua tombol/icon.
- Kontras teks minimal 4.5:1 (cek dengan tool contrast checker).
- Jangan andal warna saja (tambah ikon/label). Contoh: stok habis = badge merah + teks "Habis".
- Setiap aksi ada
SnackBarfeedback: sukses hijau, error merah, info biru/abu. - Gunakan
Semanticswidget untuk icon-only button agar screen reader bisa membaca.
3. UX untuk Kasir (Critical Path)
- Target: transaksi selesai dalam 3 tap (pilih produk → bayar → selesai).
- Jangan sembunyikan aksi utama (Bayar) di bawah banyak opsi.
- Tampilkan total, diskon, dan kembalian secara real-time.
- Gunakan chip/segmented button untuk tipe pembeli (Umum/Reseller) — jangan dropdown.
- Setiap item di keranjang harus bisa diubah qty atau tipe harga dalam 1 tap.
- Jika stok habis, matikan visual (opacity turun, disable tap) dan tampilkan badge "Habis".
- Jika stok menipis, tampilkan badge peringatan oranye sebelum habis.
- Bottom bar keranjang harus selalu terlihat, tidak tertutup keyboard.
4. Payment Flow: Bayar Nanti
- Saat checkout pilih Bayar Nanti, tombol harus jelas: "Pesanan Sekarang".
- Field "Uang Dibayar" menjadi disabled dengan label "Belum Bayar".
- Tampilkan status "Belum Bayar" di receipt/summary dengan warna
kWarning. - Di Riwayat, transaksi unpaid harus punya badge/status yang menonjol.
- Saat customer datang bayar: tombol "Terima Pembayaran" atau "Bayar Sisa" di detail transaksi.
5. Reusability & Konsistensi
- Buat widget reuseable untuk komponen berulang:
_Tag,_ReportCard,_Avatar,_Stepper,_PaymentMethodChip,_ReceiptRow,_SettingsListTile. - Jangan duplikasi warna/spacing. Taruh konstanta di
lib/utils/colors.dart. - Konsisten pakai
formatRupiah()untuk semua angka mata uang. - Nama widget internal: pakai underscore prefix (
_) kecuali widget yang diekspor.
6. Checklist Sebelum Submit Perubahan UI/UX
flutter analyze→ No issues found (tanpa error).- Coba di layar kecil (HP 5") dan besar (tablet).
- Coba dengan keyboard muncul (input uang/diskon).
- Coba mode gelap dan terang (jika supported).
- Pastikan tidak ada hardcoded height yang bikin overflow.
- Semua teks dalam Bahasa Indonesia.
- Konsisten pakai
formatRupiah()untuk angka mata uang. - Coba flow lengkap: tambah produk → checkout → cetak struk → riwayat → detail.
- Coba role kasir dan admin untuk memastikan visibility benar.
7. Referensi Project
lib/utils/colors.dart— design tokens.lib/screens/kasir_screen.dart— contoh bottom sheet cart, grid produk, dialog pembayaran.lib/screens/riwayat_screen.dart— contoh kartu laporan, filter role-based.lib/screens/pengaturan_screen.dart— contoh settings form + role-based visibility.