Komponen / Lapisan & umpan balik
Dialog
Sheet modal untuk konfirmasi dan form singkat. Menghentikan pekerjaan sampai pengguna memutuskan — pakai seperlunya.
Konfirmasi aksi merusak
`alert`: tidak tertutup saat mengeklik di luar dan diumumkan sebagai alertdialog. Tombol aksi menyebut akibatnya ("Hapus PO").
PO-2026-0146 masih berupa draf.
import { useState } from 'react';
import { Button, Dialog } from '@divistant/circca-react';
import { Trash } from '@phosphor-icons/react';
// Konfirmasi aksi merusak: alert = tidak tertutup saat klik di luar; tombol menyebut akibatnya.
export default function Example() {
const [status, setStatus] = useState('PO-2026-0146 masih berupa draf.');
return (
<div className="grid justify-items-start gap-3">
<Dialog
alert
title="Hapus PO-2026-0146?"
subtitle="Draf beserta lampirannya akan dihapus permanen dan tidak bisa dikembalikan."
trigger={
<Button variant="danger" leadingVisual={Trash}>
Hapus draf
</Button>
}
footer={
<>
<Dialog.Close asChild>
<Button>Batal</Button>
</Dialog.Close>
<Dialog.Close asChild>
<Button variant="danger" onClick={() => setStatus('PO-2026-0146 dihapus.')}>
Hapus PO
</Button>
</Dialog.Close>
</>
}
/>
<p className="type-callout-default text-muted">{status}</p>
</div>
);
}Form singkat
Maksimal ±3 isian. Lebih dari itu buka halaman form atau panel inspector (Drawer).
import { useState } from 'react';
import { Button, Dialog, FormField, Select, Textarea } from '@divistant/circca-react';
// Form singkat di dalam dialog: tombol di kanan bawah, aksi utama menyebut hasilnya.
export default function Example() {
const [open, setOpen] = useState(false);
const [termin, setTermin] = useState('50-50');
const [simpan, setSimpan] = useState<string>();
return (
<div className="grid justify-items-start gap-3">
<Button onClick={() => setOpen(true)}>Ubah termin</Button>
<Dialog
open={open}
onOpenChange={setOpen}
size="md"
title="Ubah termin PO-2026-0142"
subtitle="Perubahan termin perlu disetujui ulang oleh Finance."
footer={
<>
<Dialog.Close asChild>
<Button>Batal</Button>
</Dialog.Close>
<Button
variant="primary"
onClick={() => {
setSimpan(termin === '50-50' ? '50% saat kick-off · 50% setelah BAST' : '30% saat PO terbit · 70% setelah BAST');
setOpen(false);
}}
>
Ajukan perubahan
</Button>
</>
}
>
<div className="grid gap-4">
<FormField label="Template termin" required>
<Select
value={termin}
onValueChange={setTermin}
options={[
{ value: '50-50', label: '50% saat kick-off · 50% setelah BAST' },
{ value: '30-70', label: '30% saat PO terbit · 70% setelah BAST' },
]}
/>
</FormField>
<FormField label="Alasan perubahan" required helperText="Dikirim ke Rina Kartika (Finance).">
<Textarea maxLength={300} placeholder="Mis. customer meminta pembayaran awal lebih kecil" />
</FormField>
</div>
</Dialog>
{simpan && <p className="type-callout-default text-muted">Diajukan: {simpan}</p>}
</div>
);
}Perubahan belum disimpan
Muncul saat pengguna meninggalkan form yang berubah, mis. berpindah perusahaan. Aksi paling aman (Simpan draf) menjadi Primary.
Ubah isian ini, lalu coba pindah perusahaan.
import { useState } from 'react';
import { Button, Dialog, FormField, Input } from '@divistant/circca-react';
// Perubahan belum disimpan: tiga pilihan seperti dialog simpan macOS — Buang di kiri, Tetap di sini (fokus awal) & Simpan draf di kanan.
export default function Example() {
const [nomor, setNomor] = useState('PO/MEP/X/2026/0042');
const [tersimpan, setTersimpan] = useState(nomor);
const [tanya, setTanya] = useState(false);
const [pesan, setPesan] = useState<string>();
const kotor = nomor !== tersimpan;
const pergi = () => {
if (kotor) setTanya(true);
else setPesan('Berpindah ke PT Divistant Teknologi Indonesia.');
};
return (
<div className="grid max-w-sm gap-4">
<FormField label="No. PO customer" helperText={kotor ? 'Ada perubahan yang belum disimpan.' : 'Ubah isian ini, lalu coba pindah perusahaan.'}>
<Input value={nomor} onChange={e => setNomor(e.target.value)} />
</FormField>
<div>
<Button onClick={pergi}>Pindah ke Divistant</Button>
</div>
{pesan && <p className="type-callout-default text-muted">{pesan}</p>}
<Dialog
open={tanya}
onOpenChange={setTanya}
alert
size="md"
title="Simpan perubahan PO-2026-0146?"
subtitle="Perubahan yang belum disimpan akan hilang bila Anda pindah perusahaan sekarang."
footer={
<>
<Button
variant="ghost"
className="mr-auto"
onClick={() => {
setNomor(tersimpan);
setTanya(false);
setPesan('Perubahan dibuang. Berpindah ke PT Divistant Teknologi Indonesia.');
}}
>
Buang perubahan
</Button>
<Dialog.Close asChild>
<Button>Tetap di sini</Button>
</Dialog.Close>
<Button
variant="primary"
onClick={() => {
setTersimpan(nomor);
setTanya(false);
setPesan('Draf disimpan. Berpindah ke PT Divistant Teknologi Indonesia.');
}}
>
Simpan draf
</Button>
</>
}
/>
</div>
);
}Ukuran & tombol tutup
Small 400 untuk konfirmasi, Medium 560 untuk form, Large 720 untuk isi bertabel. Tombol × hanya untuk dialog informasi tanpa keputusan.
import { Button, Dialog } from '@divistant/circca-react';
// Small 400 untuk konfirmasi, Medium 560 untuk form singkat, Large 720 untuk isi dengan tabel kecil.
export default function Example() {
return (
<div className="flex flex-wrap gap-2">
{(['sm', 'md', 'lg'] as const).map(size => (
<Dialog
key={size}
size={size}
closeButton
title={{ sm: 'Small · 400', md: 'Medium · 560', lg: 'Large · 720' }[size]}
subtitle="Tekan Esc, tombol ×, atau klik di luar untuk menutup."
trigger={<Button>{{ sm: 'Small', md: 'Medium', lg: 'Large' }[size]}</Button>}
>
<p className="text-muted">Isi dialog bergulir sendiri bila lebih tinggi dari layar; judul dan tombol tetap terlihat.</p>
</Dialog>
))}
</div>
);
}