Dialog
Modal oyna: forma va tasdiqlash uchun, telefonda pastdan chiqadi; imperativ confirm() bilan.
'use client';
import { Button, Dialog, Field, Input } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function DialogBasic() {
const [open, setOpen] = useState(false);
const [name, setName] = useState('Sarvar');
const [phone, setPhone] = useState('+998 90 123 45 67');
return (
<Dialog open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<Button variant="primary">Profilni tahrirlash</Button>
</Dialog.Trigger>
<Dialog.Content title="Profilni tahrirlash" description="O'zgarishlar darhol saqlanadi.">
<div className="flex flex-col gap-4">
<Field label="Ism">
<Input value={name} onChange={(e) => setName(e.target.value)} />
</Field>
<Field label="Telefon" description="Tasdiqlash uchun SMS kod yuboriladi">
<Input value={phone} onChange={(e) => setPhone(e.target.value)} />
</Field>
</div>
<Dialog.Footer>
<Dialog.Close asChild>
<Button>Bekor qilish</Button>
</Dialog.Close>
<Button variant="primary" onClick={() => setOpen(false)}>
Saqlash
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
}
Import
import { Dialog, ConfirmDialog, ConfirmProvider, confirm } from '@akhatmirzo/ui';Qachon ishlatish
- Diqqatni bitta vazifaga qaratish kerak bo'lsa — qisqa forma, tafsilotlarni tahrirlash, muhim ogohlantirish. Foydalanuvchi oynani yopmaguncha sahifa bilan ishlay olmaydi.
- Xavfli amalni tasdiqlash —
confirm()yokiConfirmDialog. Bitta qator kod bilantrue/falsejavob olasiz. - Uzun forma yoki filtrlar uchun yon panel —
Drawerqulayroq: sahifa konteksti ko'rinib turadi. - Kichik, bog'lanmagan kontent (filtr, rang tanlash) uchun modal kerak emas —
Popoveroling. - Natija haqida xabar ("Saqlandi") uchun oyna ochmang —
toast()yetarli.
Tuzilishi
Dialog — holatni boshqaradi (open, defaultOpen, onOpenChange). Qolgan qismlar:
| Qism | Vazifasi |
|---|---|
Dialog.Trigger | Ochuvchi element. asChild bilan o'zingizning tugmangizni bering |
Dialog.Content | Oynaning o'zi: fon, sarlavha, izoh, ✕ tugmasi, fokus tuzog'i |
Dialog.Footer | Pastki tugmalar qatori — o'ngga tekislangan, 8px oraliq |
Dialog.Close | Bosilganda oynani yopadigan element (asChild bilan) |
<Dialog open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<Button>Ochish</Button>
</Dialog.Trigger>
<Dialog.Content title="Profilni tahrirlash" description="O'zgarishlar darhol saqlanadi.">
{/* forma */}
<Dialog.Footer>
<Dialog.Close asChild>
<Button>Bekor qilish</Button>
</Dialog.Close>
<Button variant="primary">Saqlash</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>title — majburiy: ekran o'quvchi oynani shu nom bilan e'lon qiladi. Ko'rinmasligi kerak bo'lsa,
hideTitle qo'ying, lekin olib tashlamang.
O'lchamlar
size oyna kengligini belgilaydi: sm — 400px, md — 520px (standart), lg — 640px, xl —
800px, full — butun ekran. Balandlik ekrandan 32px kam bo'lishi mumkin, kontent uzun bo'lsa
oyna ichida scroll paydo bo'ladi.
'use client';
import { Button, Dialog, type DialogSize } from '@akhatmirzo/ui';
const sizes: { size: DialogSize; width: string }[] = [
{ size: 'sm', width: '400px' },
{ size: 'md', width: '520px' },
{ size: 'lg', width: '640px' },
{ size: 'xl', width: '800px' },
{ size: 'full', width: 'butun ekran' },
];
export default function DialogSizes() {
return (
<div className="flex flex-wrap items-center gap-3">
{sizes.map(({ size, width }) => (
<Dialog key={size}>
<Dialog.Trigger asChild>
<Button variant="outline">{size}</Button>
</Dialog.Trigger>
<Dialog.Content
title={`O'lcham: ${size}`}
description={`Kenglik — ${width}.`}
size={size}
>
<p className="m-0 text-sm text-text-muted">
Ekran tor bo'lsa, chetlarda 16px joy qoladi. Telefonda oyna pastdan chiqadi.
</p>
<Dialog.Footer>
<Dialog.Close asChild>
<Button variant="primary">Tushunarli</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
))}
</div>
);
}
Sozlamalar
dismissible={false}— tashqariga bosish oynani yopmaydi (Escishlayveradi).closeButton={false}— o'ng yuqoridagi ✕ tugmasini olib tashlaydi.hideTitle— sarlavhani ko'rinmas qiladi, ekran o'quvchi uni baribir o'qiydi.mobileSheet={false}— telefonda (640px dan tor ekranda) ham oyna markazda qoladi. Standart holatda u pastdan chiqadigan sheet'ga aylanadi.
'use client';
import { Button, Dialog } from '@akhatmirzo/ui';
export default function DialogOptions() {
return (
<div className="flex flex-wrap items-center gap-3">
<Dialog>
<Dialog.Trigger asChild>
<Button variant="outline">dismissible=false</Button>
</Dialog.Trigger>
<Dialog.Content
title="Shartlarni o'qing"
description="Tashqariga bosish yopmaydi — faqat tugma yoki Esc."
dismissible={false}
>
<Dialog.Footer>
<Dialog.Close asChild>
<Button variant="primary">Roziman</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
<Dialog>
<Dialog.Trigger asChild>
<Button variant="outline">closeButton=false</Button>
</Dialog.Trigger>
<Dialog.Content title="✕ tugmasisiz" closeButton={false} size="sm">
<p className="m-0 text-sm text-text-muted">Oyna faqat pastdagi tugma bilan yopiladi.</p>
<Dialog.Footer>
<Dialog.Close asChild>
<Button variant="primary">Yopish</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
<Dialog>
<Dialog.Trigger asChild>
<Button variant="outline">hideTitle</Button>
</Dialog.Trigger>
<Dialog.Content title="Mahsulot rasmi" hideTitle size="sm">
<div className="grid h-40 place-items-center rounded-xl bg-surface-muted text-sm text-text-muted">
Sarlavha faqat ekran o'quvchi uchun
</div>
</Dialog.Content>
</Dialog>
<Dialog>
<Dialog.Trigger asChild>
<Button variant="outline">mobileSheet=false</Button>
</Dialog.Trigger>
<Dialog.Content
title="Doim markazda"
description="Telefonda ham pastdan chiqmaydi."
mobileSheet={false}
size="sm"
>
<Dialog.Footer>
<Dialog.Close asChild>
<Button variant="primary">Yaxshi</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
</div>
);
}
Yuborish paytida
Forma yuborilayotganda oynani tasodifan yopib qo'ymaslik uchun dismissible ni o'chiring va
onOpenChange da yopilishni to'xtating — shunda tashqariga bosish ham, Esc ham ishlamaydi.
'use client';
import { Button, Dialog, Field, Input, Textarea, toast } from '@akhatmirzo/ui';
import { useState } from 'react';
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function DialogAsyncSubmit() {
const [open, setOpen] = useState(false);
const [sending, setSending] = useState(false);
const send = async () => {
setSending(true);
await wait(1500);
setSending(false);
setOpen(false);
toast.success('Fikringiz yuborildi');
};
return (
<Dialog open={open} onOpenChange={(next) => !sending && setOpen(next)}>
<Dialog.Trigger asChild>
<Button>Fikr bildirish</Button>
</Dialog.Trigger>
<Dialog.Content title="Fikr bildirish" size="lg" dismissible={!sending}>
<div className="flex flex-col gap-4">
<Field label="Mavzu" required>
<Input placeholder="Masalan: yetkazib berish" />
</Field>
<Field label="Xabar">
<Textarea minRows={4} placeholder="Fikringizni yozing..." />
</Field>
</div>
<Dialog.Footer>
<Dialog.Close asChild>
<Button disabled={sending}>Bekor qilish</Button>
</Dialog.Close>
<Button variant="primary" loading={sending} onClick={send}>
Yuborish
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
}
Tasdiqlash: confirm()
confirm() — imperativ API: oynani ochadi va Promise<boolean> qaytaradi. "Tasdiqlash" —
true, "Bekor qilish", Esc yoki tashqariga bosish — false.
'use client';
import { Button, confirm, toast } from '@akhatmirzo/ui';
import { Trash2 } from 'lucide-react';
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export default function DialogConfirm() {
const removeOrder = async () => {
const ok = await confirm({
title: "Buyurtma o'chirilsinmi?",
description: "Bu amalni qaytarib bo'lmaydi.",
confirmText: "O'chirish",
variant: 'danger',
});
if (ok) toast.success("Buyurtma #1042 o'chirildi");
};
const publish = async () => {
const ok = await confirm({
title: 'Mahsulot nashr qilinsinmi?',
description: "Mahsulot do'konda hammaga ko'rinadi.",
confirmText: 'Nashr qilish',
// Promise tugaguncha tugma loading, oyna ochiq turadi
onConfirm: () => wait(1500),
});
if (ok) toast.success('Mahsulot nashr qilindi');
};
return (
<div className="flex flex-wrap items-center gap-3">
<Button variant="danger" leftIcon={<Trash2 />} onClick={removeOrder}>
O'chirish
</Button>
<Button variant="primary" onClick={publish}>
Nashr qilish
</Button>
</div>
);
}
const ok = await confirm({
title: "Buyurtma o'chirilsinmi?",
description: "Bu amalni qaytarib bo'lmaydi.",
confirmText: "O'chirish",
variant: 'danger',
});
if (ok) await deleteOrder(id);variant: 'danger'— tasdiqlash tugmasi qizil, dastlabki fokus "Bekor qilish" da: foydalanuvchiEnterni tasodifan bosib, ma'lumotni o'chirib yubormaydi.onConfirmPromise qaytarsa — tugmaloadingholatiga o'tadi, oyna javob kelguncha yopilmaydi (Escva tashqariga bosish ham ishlamaydi). Promise xato bilan tugasa, oyna ochiq qoladi — foydalanuvchi qayta urinadi yoki bekor qiladi.
ConfirmProvider shart
confirm() ishlashi uchun ilovada bir marta <ConfirmProvider /> turishi kerak. U bo'lmasa,
confirm() "ilovada <ConfirmProvider /> topilmadi" xatosi bilan rad etiladi.
'use client';
import { ConfirmProvider } from '@akhatmirzo/ui';
import type { ReactNode } from 'react';
export function Providers({ children }: { children: ReactNode }) {
return <ConfirmProvider>{children}</ConfirmProvider>;
}ConfirmProvider ilovani o'rab olishi ham, yonida yolg'iz turishi ham mumkin. Uning labels
prop'i barcha confirm() oynalari uchun standart tugma matnlarini o'rnatadi:
labels={{ confirm: 'Confirm', cancel: 'Cancel' }}.
ConfirmDialog — deklarativ variant
Tasdiqlash oynasi JSX'da turishi kerak bo'lsa (masalan, aniq trigger bilan), ConfirmDialog
dan foydalaning. Opsiyalari confirm() bilan bir xil, ustiga trigger va onCancel.
'use client';
import { Button, ConfirmDialog, toast } from '@akhatmirzo/ui';
export default function DialogConfirmDeclarative() {
return (
<ConfirmDialog
trigger={<Button variant="outline">Hisobdan chiqish</Button>}
title="Hisobdan chiqilsinmi?"
description="Qayta kirish uchun SMS kod kerak bo'ladi."
confirmText="Chiqish"
onConfirm={() => toast('Hisobdan chiqdingiz')}
onCancel={() => toast.info('Hisobda qoldingiz')}
/>
);
}
Dialog ichida Select va Combobox
Dialog.Content ichidagi Select, Combobox,
DropdownMenu va Popover modal qatlamini o'zi aniqlaydi:
paneli oyna ustida ochiladi va fokus tuzog'i ularga xalaqit bermaydi. Hech qanday z-index yoki
container sozlash shart emas. Drawer ichida ham xuddi shunday.
'use client';
import { Button, Combobox, Dialog, Field, Select } from '@akhatmirzo/ui';
import { useState } from 'react';
const cities = ['Toshkent', 'Samarqand', 'Buxoro', 'Andijon', "Farg'ona", 'Namangan'].map(
(label) => ({ value: label.toLowerCase().replace(/'/g, ''), label }),
);
export default function DialogWithSelect() {
const [city, setCity] = useState<string | null>('toshkent');
return (
<Dialog>
<Dialog.Trigger asChild>
<Button variant="outline">Yetkazib berish manzili</Button>
</Dialog.Trigger>
<Dialog.Content
title="Yetkazib berish manzili"
description="Ro'yxatlar oyna ustida ochiladi."
>
<div className="flex flex-col gap-4">
<Field label="Shahar">
<Combobox options={cities} value={city} onValueChange={setCity} clearable />
</Field>
<Field label="Yetkazish vaqti">
<Select defaultValue="morning">
<Select.Item value="morning">Ertalab (9:00–12:00)</Select.Item>
<Select.Item value="day">Kunduzi (12:00–17:00)</Select.Item>
<Select.Item value="evening">Kechqurun (17:00–21:00)</Select.Item>
</Select>
</Field>
</div>
<Dialog.Footer>
<Dialog.Close asChild>
<Button>Bekor qilish</Button>
</Dialog.Close>
<Dialog.Close asChild>
<Button variant="primary">Saqlash</Button>
</Dialog.Close>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
);
}
Props
Dialog
Radix Dialog Root prop'larini qabul qiladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
open | boolean | — | Ochiqmi (boshqariladigan rejim) |
defaultOpen | boolean | — | Boshlang'ich holatda ochiq |
onOpenChange | (open: boolean) => void | — | Ochilib-yopilganda |
modal | boolean | true | Sahifaning qolgan qismini bloklaydi |
Dialog.Content
Qolgan <div> atributlari, ref va Radix hodisalari (onEscapeKeyDown, onOpenAutoFocus,
onInteractOutside ...) panelga uzatiladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
titlemajburiy | ReactNode | — | Sarlavha — ekran o'quvchi uchun ham |
description | ReactNode | — | Sarlavha ostidagi izoh |
size | 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'md' | 400 / 520 / 640 / 800px / butun ekran |
hideTitle | boolean | false | Sarlavhani ko'rinmas qiladi (ekran o'quvchi o'qiydi) |
closeButton | boolean | true | O'ng yuqorida ✕ tugmasi |
dismissible | boolean | true | false — tashqariga bosish yopmaydi (Esc ishlaydi) |
mobileSheet | boolean | true | 640px dan tor ekranda pastdan chiqadigan sheet |
overlayClassName | string | — | Fon (overlay) uchun klass |
container | HTMLElement | null | — | Portal konteyneri (standart — document.body) |
labels | { close?: string } | { close: 'Yopish' } | ✕ tugmasining nomi |
Dialog.Trigger, Dialog.Close
Ikkalasi ham asChild qabul qiladi — o'z elementingizni (odatda Button) bering.
Dialog.Footer — oddiy <div>, barcha div atributlarini oladi.
confirm() va ConfirmDialog
confirm(options): Promise<boolean>. ConfirmDialog xuddi shu opsiyalarni prop sifatida oladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
titlemajburiy | ReactNode | — | Savol |
description | ReactNode | — | Qisqa izoh |
confirmText | ReactNode | 'Tasdiqlash' | Tasdiqlash tugmasi matni |
cancelText | ReactNode | 'Bekor qilish' | Bekor qilish tugmasi matni |
variant | 'default' | 'danger' | 'default' | danger — qizil tugma, fokus 'Bekor qilish' da |
onConfirm | () => unknown | — | Promise qaytarsa — loading, tugaguncha yopilmaydi; xatoda ochiq qoladi |
Faqat ConfirmDialog uchun:
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
trigger | ReactNode | — | Ochuvchi element |
open | boolean | — | Boshqariladigan rejim |
defaultOpen | boolean | false | Boshida ochiq |
onOpenChange | (open: boolean) => void | — | Ochilib-yopilganda |
onCancel | () => void | — | Bekor qilinganda (tugma, Esc yoki tashqariga bosish) |
labels | { confirm?: string; cancel?: string } | — | Standart tugma matnlari |
ConfirmProvider
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
labels | { confirm?: string; cancel?: string } | — | Barcha confirm() oynalari uchun standart matnlar |
children | ReactNode | — | Ixtiyoriy: ilovani o'rab olishi mumkin |
Data atributlari
| Atribut | Qayerda | Qiymat |
|---|---|---|
data-state | panel | open · closed |
data-size | panel | sm · md · lg · xl · full |
data-sheet | panel | telefonda sheet ko'rinishida bo'lganda mavjud |
Dastlabki fokusni o'zingiz tanlamoqchi bo'lsangiz, kerakli elementga data-autofocus atributini
qo'ying.
Klaviatura
| Tugma | Harakat |
|---|---|
Tab | Oyna ichidagi keyingi element (fokus oynadan chiqmaydi) |
Shift + Tab | Oldingi element |
Esc | Oynani yopadi, fokus ochgan elementga qaytadi |
Accessibility
- Rol va nom. Panel —
role="dialog"(confirm()vaConfirmDialogda —role="alertdialog"),aria-labelledbysarlavhaga,aria-describedbyizohga ulanadi. Izoh bo'lmasa,aria-describedbyumuman qo'yilmaydi. - Fokus tuzog'i. Ochiq paytda fokus oyna ichida aylanadi, sahifaning qolgan qismi ekran o'quvchidan yashiriladi va scroll qilinmaydi.
- Dastlabki fokus:
[data-autofocus]→ birinchi maydon (input,textarea,select) → birinchi tugma (✕ dan tashqari) → panelning o'zi. - Fokusni qaytarish. Yopilgach fokus
Dialog.Triggerga qaytadi. Trigger bo'lmasa (boshqariladigan rejim yokiconfirm()), oyna ochilishidan oldin fokusda bo'lgan elementga qaytadi. - ✕ tugmasi "Yopish" nomini oladi —
labels.closebilan o'zgartiriladi. - Kamroq harakat.
prefers-reduced-motionyoqilgan bo'lsa, siljish o'rniga faqat shaffoflik animatsiyasi ishlaydi.