Komponen / Aksi
PullDownButton
Tombol yang membuka menu aksi terkait: "Ekspor ▾" (Excel, PDF, CSV) di toolbar daftar, "Aksi lain ▾" di header detail PO, dan ⋯ di baris tabel.
Ekspor
Satu tombol untuk beberapa format. Secondary (bawaan) atau Ghost; Small 24 untuk toolbar padat, Medium 28 bawaan. Nonaktif opasitas 45%.
Pilih format ekspor.
import { useState } from 'react';
import { ActionMenu, PullDownButton } from '@divistant/circca-react';
import { DownloadSimple, FileCsv, FilePdf, FileXls } from '@phosphor-icons/react';
// "Ekspor ▾" di toolbar daftar: satu tombol untuk beberapa format, bukan tiga tombol berdampingan.
export default function Example() {
const [last, setLast] = useState<string | null>(null);
const menu = (
<>
<ActionMenu.Item leadingVisual={FileXls} onSelect={() => setLast('Excel')}>
Excel (.xlsx)
</ActionMenu.Item>
<ActionMenu.Item leadingVisual={FilePdf} onSelect={() => setLast('PDF')}>
PDF
</ActionMenu.Item>
<ActionMenu.Item leadingVisual={FileCsv} onSelect={() => setLast('CSV')}>
CSV
</ActionMenu.Item>
</>
);
return (
<div className="grid gap-4">
<div className="flex flex-wrap items-center gap-3">
<PullDownButton label="Ekspor" leadingVisual={DownloadSimple}>
{menu}
</PullDownButton>
<PullDownButton label="Ekspor" size="sm">
{menu}
</PullDownButton>
<PullDownButton label="Ekspor" variant="ghost">
{menu}
</PullDownButton>
<PullDownButton label="Ekspor" variant="ghost" size="sm" disabled>
{menu}
</PullDownButton>
</div>
<p className="type-callout-default text-muted" aria-live="polite">
{last ? `Mengekspor 24 PO ke ${last}…` : 'Pilih format ekspor.'}
</p>
</div>
);
}Aksi lain di header detail
Aksi utama tetap tombol sendiri; aksi sekunder dikumpulkan di "Aksi lain ▾". Batalkan PO dikonfirmasi Dialog `alert`.
import { useState } from 'react';
import { ActionMenu, Button, Dialog, PullDownButton } from '@divistant/circca-react';
import { Copy, PaperPlaneTilt, PencilSimple, Printer, XCircle } from '@phosphor-icons/react';
// Header detail PO: aksi utama tetap tombol sendiri, aksi sekunder dikumpulkan di "Aksi lain ▾".
export default function Example() {
const [confirm, setConfirm] = useState(false);
return (
<div className="flex flex-wrap items-center gap-2">
<PullDownButton label="Aksi lain" align="end">
<ActionMenu.Item leadingVisual={Copy} shortcut="⌘D">
Duplikat PO
</ActionMenu.Item>
<ActionMenu.Item leadingVisual={Printer} shortcut="⌘P">
Cetak
</ActionMenu.Item>
<ActionMenu.Item leadingVisual={PaperPlaneTilt}>Kirim ke customer</ActionMenu.Item>
<ActionMenu.Separator />
<ActionMenu.Item variant="danger" leadingVisual={XCircle} onSelectAfterClose={() => setConfirm(true)}>
Batalkan PO…
</ActionMenu.Item>
</PullDownButton>
<Button leadingVisual={PencilSimple}>Ubah</Button>
<Button variant="primary">Buat delivery note</Button>
<Dialog
alert
open={confirm}
onOpenChange={setConfirm}
title="Batalkan PO-2026-0142?"
subtitle="PO tidak bisa diproses lagi. Tindakan ini tercatat di riwayat dokumen."
footer={
<>
<Dialog.Close asChild>
<Button>Kembali</Button>
</Dialog.Close>
<Button variant="danger" onClick={() => setConfirm(false)}>
Batalkan PO
</Button>
</>
}
/>
</div>
);
}Ikon saja di baris tabel
Isi `icon` (mis. DotsThree) untuk tombol bulat tanpa chevron — Ghost Small, menu rata kanan. `label` menjadi aria-label dan tooltip.
| No. PO | Customer | Total | Aksi |
|---|---|---|---|
| PO-2026-0142 | PT Sinar Logistik | Rp 148.500.000 | |
| PO-2026-0143 | CV Mitra Data | Rp 36.200.000 | |
| PO-2026-0144 | PT Arunika Retail | Rp 92.750.000 |
import { useState } from 'react';
import { Button, Dialog, PullDownButton, type ActionMenuEntry } from '@divistant/circca-react';
import { Copy, DotsThree, Eye, Printer, XCircle } from '@phosphor-icons/react';
// Varian ikon saja (⋯) untuk aksi per baris tabel: Ghost Small, menu rata kanan.
// Label aksesibel menyebut barisnya agar pembaca layar membedakan tiap tombol.
const rows = [
{
no: 'PO-2026-0142',
customer: 'PT Sinar Logistik',
total: 'Rp 148.500.000',
},
{ no: 'PO-2026-0143', customer: 'CV Mitra Data', total: 'Rp 36.200.000' },
{ no: 'PO-2026-0144', customer: 'PT Arunika Retail', total: 'Rp 92.750.000' },
];
const aksi = (no: string, batalkan: (no: string) => void): ActionMenuEntry[] => [
{ label: 'Lihat detail', leadingVisual: Eye },
{ label: 'Duplikat PO', leadingVisual: Copy },
{ label: 'Cetak', leadingVisual: Printer },
{ type: 'separator' },
{
label: 'Batalkan PO…',
leadingVisual: XCircle,
variant: 'danger',
onSelectAfterClose: () => batalkan(no),
},
];
export default function Example() {
const [cancelNo, setCancelNo] = useState<string | null>(null);
return (
<>
<table className="w-full max-w-xl type-callout-default text-default">
<thead>
<tr className="border-b border-muted text-left type-footnote-medium text-muted">
<th className="py-2 font-normal">No. PO</th>
<th className="py-2 font-normal">Customer</th>
<th className="py-2 text-right font-normal">Total</th>
<th className="w-10 py-2">
<span className="sr-only">Aksi</span>
</th>
</tr>
</thead>
<tbody>
{rows.map(r => (
<tr key={r.no} className="border-b border-muted">
<td className="py-1.5">{r.no}</td>
<td className="py-1.5">{r.customer}</td>
<td className="py-1.5 text-right tabular-nums">{r.total}</td>
<td className="py-1.5 text-right">
<PullDownButton
label={`Aksi lain untuk ${r.no}`}
icon={DotsThree}
variant="ghost"
size="sm"
align="end"
items={aksi(r.no, setCancelNo)}
/>
</td>
</tr>
))}
</tbody>
</table>
<Dialog
alert
open={cancelNo != null}
onOpenChange={o => !o && setCancelNo(null)}
title={`Batalkan ${cancelNo ?? ''}?`}
subtitle="PO tidak bisa diproses lagi setelah dibatalkan."
footer={
<>
<Dialog.Close asChild>
<Button>Kembali</Button>
</Dialog.Close>
<Button variant="danger" onClick={() => setCancelNo(null)}>
Batalkan PO
</Button>
</>
}
/>
</>
);
}