Lompat ke konten
dokumentasi.id
Commit 9a47d33
v2
Star3,4 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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:24, src/components/buttons/IconButton.tsx:26, src/components/buttons/TextButton.tsx:11. Semua penggabungan class melewati satu helper, cn, yang membungkus clsx dengan twMerge src/lib/utils.ts:5-7.

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:22, src/components/buttons/IconButton.tsx:24, src/components/buttons/TextButton.tsx:9.

PropButtonIconButtonTextButtonRujukan
Nilai variantprimary, outline, ghost, light, darksama dengan Buttonprimary, basicsrc/components/buttons/Button.tsx:8-9, src/components/buttons/IconButton.tsx:8-14, src/components/buttons/TextButton.tsx:5
Nilai defaultvariant='primary', isDarkBg=falsevariant='primary', isDarkBg=falsevariant='primary'src/components/buttons/Button.tsx:31-33, src/components/buttons/IconButton.tsx:32-33, src/components/buttons/TextButton.tsx:16
sizesm dan basetidak adatidak adasrc/components/buttons/Button.tsx:9
isLoadingadaadatidak adasrc/components/buttons/Button.tsx:12, src/components/buttons/IconButton.tsx:17, src/components/buttons/TextButton.tsx:7-9
isDarkBgadaadatidak adasrc/components/buttons/Button.tsx:13, src/components/buttons/IconButton.tsx:18
IkonleftIcon dan rightIconicontidak adasrc/components/buttons/Button.tsx:16-17, src/components/buttons/IconButton.tsx:20
classNamesleftIcon, rightIconicontidak adasrc/components/buttons/Button.tsx:18-21, src/components/buttons/IconButton.tsx:21-23

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.
Fig. 1: Perakitan className pada ButtonProp 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-52. Di bawahnya size memilih padding dan ukuran teks src/components/buttons/Button.tsx:54-57, lalu variant memilih satu dari lima cabang src/components/buttons/Button.tsx:61-92. Argumen className dari pemanggil diletakkan paling akhir, sehingga twMerge di dalam cn bisa menimpanya src/components/buttons/Button.tsx:95-99.

Lima variant

Tabel berikut merangkum class inti yang dipasang Button untuk setiap nilai variant src/components/buttons/Button.tsx:61-92.

variantLatar dan teksHover, active, disabled
primarybg-primary-500 text-white dengan border-primary-600hover:bg-primary-600, active:bg-primary-700, disabled:bg-primary-700
outlinetext-primary-500 dengan border-primary-500hover:bg-primary-50, active:bg-primary-100, disabled:bg-primary-100
ghosttext-primary-500 dan shadow-nonehover:bg-primary-50, active:bg-primary-100, disabled:bg-primary-100
lightbg-white text-gray-700 dengan border-gray-300hover:bg-gray-100, active:bg-white/80, disabled:bg-gray-200
darkbg-gray-900 text-white dengan border-gray-600hover:bg-gray-800, active:bg-gray-700, disabled:bg-gray-700

IconButton memakai daftar class yang sama persis src/components/buttons/IconButton.tsx:55-86. 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-74, src/components/buttons/IconButton.tsx:62-68.

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-57.

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-134, src/components/buttons/Button.tsx:136-154. 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:52. Isinya satu ikon Icon berukuran 1em yang class-nya bisa ditimpa lewat classNames.icon src/components/buttons/IconButton.tsx:96-110.

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.
Fig. 2: Status tombol Button dan IconButtonInteraksi 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:41, src/components/buttons/IconButton.tsx:40. Ketika isLoading aktif, teks tombol dibuat transparan lewat text-transparent dan kursor menjadi disabled:cursor-wait src/components/buttons/Button.tsx:95-99, src/components/buttons/IconButton.tsx:89-93.

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-115. 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-110.

⚠️ TextButton tidak menerima isLoading src/components/buttons/TextButton.tsx:7-9. Jangan mengharapkan spinner di tombol teks; status loading harus dikelola pemanggil lewat disabled. Selain itu class disabled:hover:underline tetap terpasang, sehingga tombol teks yang nonaktif masih mendapat garis bawah saat di-hover src/components/buttons/TextButton.tsx:41.

TextButton

TextButtonVariant hanya berisi primary dan basic, dengan primary sebagai default src/components/buttons/TextButton.tsx:5, src/components/buttons/TextButton.tsx:16. 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-39. Komponen ini meneruskan disabled apa adanya tanpa menghitung status gabungan src/components/buttons/TextButton.tsx:22-26.

Salah satu pemakaian nyatanya ada di error boundary, dengan variant='basic' dan onClick={reset} src/app/error.tsx:31-33. 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-248, lalu baris disabled dan isLoading untuk variant yang sama src/app/components/page.tsx:343-376. TextButton dan IconButton mendapat blok sendiri, dan IconButton diuji dengan ikon Plus, Laptop, Phone, Shield, serta CreditCard src/app/components/page.tsx:378-400.

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.