18

UI Development Guidelines

Updated: Nov 2026Oleh: SystemSistem ERP & Teknologi Inti

UI Development Guidelines for ERP BUMDes Bolulango (v2026.5)

Dokumen ini berisi standar teknis (Technical Standards) yang wajib dipatuhi oleh seluruh pengembang dan AI Agent ketika membuat atau memodifikasi antarmuka pengguna (UI) di ERP BUMDes Bolulango (berpasangan dengan DESIGN_SYSTEM.md dan rule .agents/rules/standar_desain_ui_ux_erp.md).


1. Sistem Notifikasi (Toasts)#

ERP BUMDes menggunakan pustaka sonner sebagai sistem standar untuk notifikasi (Toast), BUKAN pustaka useToast bawaan asli dari Shadcn UI.

❌ Jangan Gunakan (Incorrect):

tsx
// JANGAN GUNAKAN INI
import { useToast } from '@/components/ui/use-toast'

export default function MyComponent() {
 const { toast } = useToast()
 
 const handleAction = () => {
 toast({ title: 'Berhasil', description: 'Data disimpan.' })
 }
}

Alasan: Karena komponen <Toaster /> dari Shadcn tidak di-mount di app/layout.tsx, sehingga toast tidak akan pernah muncul di layar (silent fail).

✅ Gunakan Ini (Correct):

tsx
// GUNAKAN INI
import { toast } from 'sonner'

export default function MyComponent() {
 const handleAction = () => {
 toast.success('Berhasil', { description: 'Data disimpan.' })
 // Atau untuk error:
 // toast.error('Gagal', { description: 'Terjadi kesalahan.' })
 }
}

Alasan: Komponen <Toaster /> dari sonner sudah di-mount secara global di app/layout.tsx sehingga notifikasi dijamin akan muncul di seluruh aplikasi.


2. Server Actions & Penanganan Error UI#

Setiap kali komponen klien (Client Component) memanggil Server Action, pastikan untuk:

  1. Membungkus panggilan menggunakan try/catch.
  2. Menyediakan state loading (seperti isSaving atau isLoading) yang di-set kembali ke false di dalam blok finally {}.
  3. Menampilkan notifikasi rincian error (toast.error) jika terjadi Exception agar UI tidak terkesan membeku (freeze).

3. Dual-Mode <Card> Architecture (v2026.5), Named-Group group/card, & Dark Mode Shadow Hygiene#

  1. Pisahkan Kartu Metrik Interaktif vs Kontainer Tabel/Dokumen Statis:
  • Kartu Metrik / KPI (<Card glowColor="emerald|blue|amber|rose|indigo|purple|sky">): Wajib menyertakan prop glowColor semantik yang cocok dengan warna ikon/kategori metriknya. Jangan membiarkan kartu KPI non-emerald jatuh ke default emerald.
  • Kontainer Tabel Data, Kertas Kerja Laporan (id="*-report-doc"), & Filter Bar (<Card glowColor="none">): Wajib menyertakan glowColor="none" agar kontainer tabel tidak melompat (hover:-translate-y-0.5) dan tidak memancarkan gradasi hijau di seluruh tabel saat pengguna menggerakkan kursor di atas baris data.
  1. Isolasi Named-Group (group/card) & Top Specular Glass Edge:
  • Komponen Card.tsx menggunakan group/card (group-hover/card:opacity-100) agar efek hover kartu tidak menginterferensi group pada baris tabel (<tr>) di dalamnya, serta menyertakan kilau atas dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.06)].
  1. Zero-Resting-Tint pada Kartu KPI & Anti-Black-Hole Table Headers:
  • Dilarang membuat kartu KPI dengan latar hitam statis (bg-slate-900 text-white), blok warna solid (bg-emerald-600 text-white), border tebal sepihak (border-l-4, border-b-4), atau gradasi statis (from-blue-50/50, dark:from-blue-950/20).
  • Dilarang menggunakan bg-slate-900 dark:bg-black text-white pada <thead> atau <tfoot> tabel laporan keuangan; gunakan bg-slate-50/90 dark:bg-white/[0.03] untuk <thead> dan bg-slate-100/90 dark:bg-white/[0.04] untuk <tfoot>.
  1. Dilarang Menggunakan border-none / border-0 pada <Card> Interaktif:
  • Menghapus border akan mematikan efek garis tepi tipis berwarna saat di-hover.
  1. Anti-Light-Shadow-Leak di Mode Gelap:
  • Setiap penggunaan bayangan pastel terang (shadow-slate-50|100|200|300, shadow-indigo-100|200, shadow-emerald-100|200, dll.) wajib menyertakan dark:shadow-none (atau bayangan semantik/gelap eksplisit) agar tidak memunculkan bayangan putih silau di atas tema gelap.
  1. Pola Kontainer Bersarang Shared-Surface Unit Leaderboard (Anti-Bolong / Anti-Sunken Pits):
  • Dilarang keras menggunakan kelas latar kanvas dasar (dark:bg-[#202020]) pada elemen anak, baris item daftar, kotak siklus alur kerja, maupun kotak metrik yang berada di dalam kartu induk <Card> (berlatar #2B2B2B) atau modal. Penempatan #202020 di dalam #2B2B2B menciptakan ilusi optik kartu yang bolong tembus pandang ke lantai halaman di bawahnya.
  • Kotak bersarang wajib menggunakan pola Shared Surface (mewarisi #2B2B2B secara alami / rgba(0,0,0,0)), dibingkai garis tepi tipis border border-slate-200/80 dark:border-white/10, dan berpendar lembut ke dark:hover:bg-[#323232] saat interaksi (mengikuti desain Windows 11 Personalisation Settings & Unit Leaderboard PerformanceRanking.tsx). Wadah ikon/lencana anak menggunakan bg-slate-100 dark:bg-white/5 border border-slate-200/60 dark:border-white/10.

4. Standar Arsitektur App Shell (Sidebar, TopBar, WorkContextBar), Neutral Charcoal Dark Mode, & 2-Layer Leaderboard#

  1. Borderless Unified Canvas (#F6F9F8 Light / #202020 Dark Neutral Charcoal) & Anti-Black-Forest:
  • Jangan pernah menambahkan border-r pada <aside> di Sidebar.tsx, border-b pada <header> di TopBar.tsx, atau border-b pada WorkContextBar.tsx.
  • Seluruh halaman, kartu, tabel, drawer, dan modal popup di Mode Gelap wajib mengikuti 4-Lapisan Ritme Warna Neutral Charcoal: Kanvas #202020 (bg-background), Kartu/Tabel/Modal #2B2B2B (bg-card), Kontrol Input/Search Pill/Menu Aktif #2D2D2D–#323232, serta Border #383838 (dark:border-white/10) & Scrollbar #484848. Dilarang keras menggunakan hex Black Forest (#070D0A, #13201B, #2d5c44).
  • Pastikan <nav> di Sidebar.tsx selalu memiliki kelas .no-scrollbar dan app/globals.css menjaga ::-webkit-scrollbar-track { background: transparent; }.
  1. Mode Ciut Sidebar (collapsed === true):
  • Saat collapsed bernilai true, jangan pernah memberikan style paddingLeft inline atau kelas truncate pada pembungkus ikon menu utama agar tombol ikon w-10 h-10 mx-auto justify-center p-0 tetap berada presisi di tengah kolom w-16.
  1. TopBar Unified Glass Pill Pod (h-9) & Zero-Duplicate Icons:
  • Pertahankan tinggi seragam h-9 (36px) untuk Search Pill, Utility Pod, dan Profile Pill di TopBar.tsx.
  • Jangan menduplikasi switcher Mode Latihan di TopBar (sudah ada di Footer Sidebar), dan jangan menggunakan <Sparkles /> lebih dari satu kali di TopBar.tsx (gunakan <Maximize2 /> untuk tombol Zen Mode).
  1. 2-Layer Stacked Leaderboard (PerformanceRanking.tsx):
  • Gunakan tata letak 2-lapis (Baris 1: Identitas Unit utuh 2 baris + Status; Baris 2: Grid 3 kolom metrik dengan format ringkas Rp XX,XX Jt + tooltip title nominal penuh) agar teks nama unit dan angka keuangan tidak pernah saling bertabrakan.

5. Pencegahan Infinite Re-render Loop pada useEffect (Maximum update depth exceeded)#

  1. Wajib Membungkus Pohon Navigasi / Array Konfigurasi Dinamis dengan useMemo:
  • Jika sebuah komponen membangun array konfigurasi bersarang (seperti navigation dan unitDirectorySubmenus di Sidebar.tsx), wajib bungkus dengan useMemo agar tidak menciptakan referensi array baru pada setiap siklus render.
  1. Wajib Memeriksa Kesamaan Elemen Sebelum Memanggil setState di Dalam useEffect:
  • Dilarang memanggil setExpandedMenus(prev => Array.from(new Set([...prev, item.name]))) langsung di dalam perulangan .forEach() pada useEffect, karena Array.from(...) selalu mengembalikan referensi array baru dan memicu render tanpa henti (infinite loop).
  • Gunakan pola aman berikut:
tsx
useEffect(() => {
const toExpand = new Set<string>()
// Kumpulkan semua item terlebih dahulu...
if (toExpand.size === 0) return
setExpandedMenus(prev => {
const missing = Array.from(toExpand).filter(name => !prev.includes(name))
if (missing.length === 0) return prev // Kembalikan referensi prev yang sama!
return [...prev, ...missing]
})
}, [pathname, navigation])

Apakah panduan ini membantu Anda?