7.1
Komponen Tombol
Tiga komponen tombol di src/components/buttons/ adalah Button, IconButton, dan TextButton. Ketiganya merender elemen <button> bertipe type='button' dan meneruskan ref ke HTMLButtonElement lewat React.forwardRef src/components/buttons/Button.tsx:241, src/components/buttons/IconButton.tsx:262, src/components/buttons/TextButton.tsx:113. Semua penggabungan class melewati satu helper, cn, yang membungkus clsx dengan twMerge src/lib/utils.ts:5-74.
Kemiripan berhenti di lapisan itu. Button dan IconButton berbagi lima variant dan status isLoading, sedangkan TextButton hanya punya dua variant dan tidak mengenal isLoading sama sekali.
Perbandingan prop
Setiap tipe prop diperluas dengan React.ComponentPropsWithRef<'button'>, jadi atribut bawaan seperti onClick dan disabled tetap lolos ke elemen aslinya src/components/buttons/Button.tsx:225, src/components/buttons/IconButton.tsx:246, src/components/buttons/TextButton.tsx:97.
Bagaimana class tombol disusun
Satu pemanggilan cn menjadi tempat seluruh prop bertemu sebelum menjadi atribut className.
graph TD
P1[variant] --> C[cn]
P2[size] --> C
P3[isDarkBg] --> C
P4[isLoading] --> C
P5[className dari pemanggil] --> C
C --> B1[class dasar inline-flex rounded-sm shadow-xs]
C --> B2[padding dan text size]
C --> B3[lima cabang variant]
C --> B4[text-transparent dan cursor-wait]
B3 --> B5[outline dan ghost saat isDarkBg]
style C fill:#f59e0b,color:#000
style B1 fill:#2d7d46,color:#fff
style B2 fill:#2d7d46,color:#fff
style B3 fill:#2d7d46,color:#fff
style B4 fill:#8b5cf6,color:#fff
style B5 fill:#8b5cf6,color:#fff
%% fig: Perakitan className pada Button
%% desc: Prop variant, size, isDarkBg, isLoading, dan className pemanggil diolah cn menjadi kelompok class, dan twMerge membuat className pemanggil menang saat bentrok.Class dasar inline-flex items-center rounded-sm font-medium, ring fokus focus-visible:ring-primary-500, shadow-xs, dan transition-colors duration-75 selalu terpasang src/components/buttons/Button.tsx:48-5224. Di bawahnya size memilih padding dan ukuran teks src/components/buttons/Button.tsx:54-5725, lalu variant memilih satu dari lima cabang src/components/buttons/Button.tsx:61-9226. Argumen className dari pemanggil diletakkan paling akhir, sehingga twMerge di dalam cn bisa menimpanya src/components/buttons/Button.tsx:95-9927.
Lima variant
Tabel berikut merangkum class inti yang dipasang Button untuk setiap nilai variant src/components/buttons/Button.tsx:61-9226.
variant | Latar dan teks | Hover, active, disabled |
|---|---|---|
primary | bg-primary-500 text-white dengan border-primary-600 | hover:bg-primary-600, active:bg-primary-700, disabled:bg-primary-700 |
outline | text-primary-500 dengan border-primary-500 | hover:bg-primary-50, active:bg-primary-100, disabled:bg-primary-100 |
ghost | text-primary-500 dan shadow-none | hover:bg-primary-50, active:bg-primary-100, disabled:bg-primary-100 |
light | bg-white text-gray-700 dengan border-gray-300 | hover:bg-gray-100, active:bg-white/80, disabled:bg-gray-200 |
dark | bg-gray-900 text-white dengan border-gray-600 | hover:bg-gray-800, active:bg-gray-700, disabled:bg-gray-700 |
IconButton memakai daftar class yang sama persis src/components/buttons/IconButton.tsx:55-8628. Satu-satunya tambahan ada pada outline dan ghost: ketika isDarkBg bernilai true, keduanya memakai hover:bg-gray-900, active:bg-gray-800, dan disabled:bg-gray-800 src/components/buttons/Button.tsx:68-7429, src/components/buttons/IconButton.tsx:62-6830.
Ukuran, ikon, dan spinner
size hanya dimiliki Button. Nilai base memberi px-3 py-1.5 dengan text-sm md:text-base, sedangkan sm memberi px-2 py-1 dengan text-xs md:text-sm src/components/buttons/Button.tsx:54-5725.
Ikon kiri dan kanan dibungkus div dengan margin mr-1 atau ml-1 untuk size='base' dan mr-1.5 atau ml-1.5 untuk sm, serta dirender berukuran 1em src/components/buttons/Button.tsx:116-13431, src/components/buttons/Button.tsx:136-15432. Class tambahan untuk tiap ikon masuk lewat classNames.leftIcon dan classNames.rightIcon.
IconButton tidak punya size; ukurannya dikunci oleh min-h-[28px] min-w-[28px] p-1 dan md:min-h-[34px] md:min-w-[34px] md:p-2 src/components/buttons/IconButton.tsx:5233. Isinya satu ikon Icon berukuran 1em yang class-nya bisa ditimpa lewat classNames.icon src/components/buttons/IconButton.tsx:96-11034.
Siklus status tombol
Hover dan active adalah status CSS, sedangkan isLoading dan disabled diputuskan di dalam komponen sebelum class ditulis.
stateDiagram-v2
[*] --> Idle
Idle --> Hover: pointer masuk
Hover --> Active: mousedown
Active --> Hover: mouseup
Hover --> Idle: pointer keluar
Idle --> Loading: isLoading true
Loading --> Idle: isLoading false
Idle --> Disabled: buttonDisabled true
Hover --> Disabled: buttonDisabled true
Loading --> Disabled: buttonDisabled true
Disabled --> Idle: prop dilepas
note right of Loading
disabled = isLoading OR buttonDisabled
teks memakai text-transparent
ImSpinner2 berputar dengan animate-spin
end note
note right of Disabled
disabled:cursor-not-allowed
isLoading memakai disabled:cursor-wait
end note
style Idle fill:#2d7d46,color:#fff
style Hover fill:#2d7d46,color:#fff
style Active fill:#2d7d46,color:#fff
style Loading fill:#f59e0b,color:#000
style Disabled fill:#dc2626,color:#fff
%% fig: Status tombol Button dan IconButton
%% desc: Interaksi hover dan active berjalan selama tombol aktif, sedangkan isLoading dan buttonDisabled memindahkan tombol ke status Loading atau Disabled.Nilai disabled pada elemen dihitung dari dua sumber, yaitu const disabled = isLoading || buttonDisabled src/components/buttons/Button.tsx:4135, src/components/buttons/IconButton.tsx:4036. Ketika isLoading aktif, teks tombol dibuat transparan lewat text-transparent dan kursor menjadi disabled:cursor-wait src/components/buttons/Button.tsx:95-9927, src/components/buttons/IconButton.tsx:89-9337.
Spinner hanya muncul saat isLoading bernilai true. ImSpinner2 dirender di dalam div berposisi absolut di tengah tombol dan diberi animate-spin src/components/buttons/Button.tsx:102-11538. Warnanya mengikuti variant: text-white untuk primary dan dark, text-black untuk light, serta text-primary-500 untuk outline dan ghost src/components/buttons/Button.tsx:107-11039.
⚠️
TextButtontidak menerimaisLoadingsrc/components/buttons/TextButton.tsx:7-917. Jangan mengharapkan spinner di tombol teks; status loading harus dikelola pemanggil lewatdisabled. Selain itu classdisabled:hover:underlinetetap terpasang, sehingga tombol teks yang nonaktif masih mendapat garis bawah saat di-hover src/components/buttons/TextButton.tsx:4140.
TextButton
TextButtonVariant hanya berisi primary dan basic, dengan primary sebagai default src/components/buttons/TextButton.tsx:510, src/components/buttons/TextButton.tsx:1613. Varian primary memberi text-primary-500 hover:text-primary-600 active:text-primary-700 disabled:text-primary-200, sedangkan basic memberi text-black hover:text-gray-600 active:text-gray-800 disabled:text-gray-300 src/components/buttons/TextButton.tsx:32-3941. Komponen ini meneruskan disabled apa adanya tanpa menghitung status gabungan src/components/buttons/TextButton.tsx:22-2642.
Salah satu pemakaian nyatanya ada di error boundary, dengan variant='basic' dan onClick={reset} src/app/error.tsx:31-3343. Rincian error boundary ada di Halaman, Error Boundary, dan Endpoint API.
Pemakaian di halaman katalog
Halaman src/app/components/page.tsx memamerkan ketiga komponen. Blok Button menampilkan lima variant src/app/components/page.tsx:233-24844, lalu baris disabled dan isLoading untuk variant yang sama src/app/components/page.tsx:343-37645. TextButton dan IconButton mendapat blok sendiri, dan IconButton diuji dengan ikon Plus, Laptop, Phone, Shield, serta CreditCard src/app/components/page.tsx:378-40046.
Untuk tombol berbentuk tautan, gunakan ButtonLink, IconLink, dan ArrowLink yang dibahas di Komponen Link. Token primary-50 hingga primary-700 yang dipakai semua variant dijelaskan di Styling, Token Warna, dan Tema.