Drawer
Ekran chetidan chiqadigan panel: filtrlar, mobil menyu va tafsilotlar uchun.
'use client';
import { Button, Checkbox, Drawer, Field, Input, Switch } from '@akhatmirzo/ui';
import { SlidersHorizontal } from 'lucide-react';
import { useState } from 'react';
const categories = ['Elektronika', 'Kiyim-kechak', "Uy-ro'zg'or", 'Kitoblar'];
export default function DrawerFilters() {
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<string[]>(['Elektronika']);
const toggle = (name: string, on: boolean) =>
setSelected((prev) => (on ? [...prev, name] : prev.filter((x) => x !== name)));
return (
<Drawer open={open} onOpenChange={setOpen}>
<Drawer.Trigger asChild>
<Button variant="outline" leftIcon={<SlidersHorizontal />}>
Filtrlar
</Button>
</Drawer.Trigger>
<Drawer.Content title="Filtrlar" description="Natijalarni toraytiring.">
<div className="flex flex-col gap-5">
<div className="grid grid-cols-2 gap-3">
<Field label="Narxdan">
<Input inputMode="numeric" placeholder="0" suffix="so'm" />
</Field>
<Field label="Narxgacha">
<Input inputMode="numeric" placeholder="1 000 000" suffix="so'm" />
</Field>
</div>
<div className="flex flex-col gap-3">
{categories.map((c) => (
<Checkbox
key={c}
label={c}
checked={selected.includes(c)}
onCheckedChange={(on) => toggle(c, on)}
/>
))}
</div>
<Switch label="Faqat mavjudlari" defaultChecked />
</div>
<Drawer.Footer>
<Button onClick={() => setSelected([])}>Tozalash</Button>
<Button variant="primary" onClick={() => setOpen(false)}>
Qo'llash
</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer>
);
}
Import
import { Drawer } from '@akhatmirzo/ui';Qachon ishlatish
- Filtrlar paneli — ko'p maydonli filtrlar sahifani yopib qo'ymasdan yon tomonda turadi.
- Tafsilotlar ko'rinishi — jadvaldagi buyurtma yoki mijozni bosganda ma'lumotlarini ochish.
- Mobil menyu —
side="left"bilan chapdan chiqadigan navigatsiya. - Qisqa forma yoki tasdiqlash uchun
Dialogto'g'riroq — diqqat markazda bo'ladi. - Drawer ham modal: ochiq paytda sahifa bloklanadi. Sahifa bilan parallel ishlash kerak bo'lsa, oddiy yon panel (layout) yasang.
Tuzilishi
API Dialog bilan bir xil — qismlar nomi ham, xulqi ham.
| Qism | Vazifasi |
|---|---|
Drawer | Holat: open, defaultOpen, onOpenChange |
Drawer.Trigger | Ochuvchi element (asChild bilan) |
Drawer.Content | Panel: fon, sarlavha, izoh, ✕ tugmasi, fokus tuzog'i |
Drawer.Footer | Pastki tugmalar — panel tubiga yopishadi |
Drawer.Close | Bosilganda panelni yopadi (asChild bilan) |
<Drawer>
<Drawer.Trigger asChild>
<Button>Filtrlar</Button>
</Drawer.Trigger>
<Drawer.Content title="Filtrlar" description="Natijalarni toraytiring.">
{/* maydonlar */}
<Drawer.Footer>
<Drawer.Close asChild>
<Button>Bekor qilish</Button>
</Drawer.Close>
<Button variant="primary">Qo'llash</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer>Tomonlar
side — panel qaysi chetdan chiqishi: right (standart), left, bottom, top. Pastdan
chiqadigan panelning tepasida tutqich chizig'i bo'ladi, burchaklari yumaloqlanadi.
'use client';
import { Button, Drawer, type DrawerSide } from '@akhatmirzo/ui';
const sides: { side: DrawerSide; label: string }[] = [
{ side: 'right', label: "O'ngdan" },
{ side: 'left', label: 'Chapdan' },
{ side: 'bottom', label: 'Pastdan' },
{ side: 'top', label: 'Tepadan' },
];
export default function DrawerSides() {
return (
<div className="flex flex-wrap items-center gap-3">
{sides.map(({ side, label }) => (
<Drawer key={side}>
<Drawer.Trigger asChild>
<Button variant="outline">{label}</Button>
</Drawer.Trigger>
<Drawer.Content side={side} title={label} description={`side="${side}"`}>
<p className="m-0 text-sm text-text-muted">
Panel ekran chetidan siljib chiqadi. Pastdan chiqqanda tepada tutqich chizig'i bor.
</p>
<Drawer.Footer>
<Drawer.Close asChild>
<Button variant="primary">Yopish</Button>
</Drawer.Close>
</Drawer.Footer>
</Drawer.Content>
</Drawer>
))}
</div>
);
}
O'lchamlar
size — sm (320px), md (420px, standart), lg (560px). Chap va o'ng tomonda bu kenglik,
past va tepada — balandlik. Tor ekranda panel ekrandan kengayib ketmaydi.
'use client';
import { Button, Drawer, type DrawerSize } from '@akhatmirzo/ui';
const sizes: { size: DrawerSize; px: string }[] = [
{ size: 'sm', px: '320px' },
{ size: 'md', px: '420px' },
{ size: 'lg', px: '560px' },
];
export default function DrawerSizes() {
return (
<div className="flex flex-wrap items-center gap-3">
{sizes.map(({ size, px }) => (
<Drawer key={size}>
<Drawer.Trigger asChild>
<Button variant="outline">
{size} — {px}
</Button>
</Drawer.Trigger>
<Drawer.Content size={size} title={`O'lcham: ${size}`} description={`Kenglik ${px}`}>
<p className="m-0 text-sm text-text-muted">
Chap va o'ngda o'lcham kenglikni, past va tepada balandlikni belgilaydi.
</p>
</Drawer.Content>
</Drawer>
))}
</div>
);
}
Mobil menyu
Navigatsiya uchun side="left" va size="sm". Sarlavha ko'rinmasligi kerak bo'lsa hideTitle
qo'ying — ekran o'quvchi uni baribir o'qiydi. Har bir havolani Drawer.Close asChild ga o'rasangiz,
bosilganda menyu o'zi yopiladi.
'use client';
import { Button, Drawer } from '@akhatmirzo/ui';
import { Menu } from 'lucide-react';
import { useState } from 'react';
const items = ['Bosh sahifa', 'Buyurtmalar', 'Mahsulotlar', 'Mijozlar', 'Hisobotlar'];
export default function DrawerMobileMenu() {
const [active, setActive] = useState('Buyurtmalar');
return (
<Drawer>
<Drawer.Trigger asChild>
<Button variant="ghost" leftIcon={<Menu />}>
Menyu
</Button>
</Drawer.Trigger>
<Drawer.Content
side="left"
size="sm"
title="Menyu"
hideTitle
labels={{ close: 'Menyuni yopish' }}
>
<nav aria-label="Asosiy menyu" className="-mx-2 flex flex-col gap-1">
{items.map((item) => (
<Drawer.Close asChild key={item}>
<button
type="button"
aria-current={item === active ? 'page' : undefined}
onClick={() => setActive(item)}
className="focus-ring flex h-10 cursor-pointer items-center rounded-lg border-0 bg-transparent px-3 text-left text-sm font-medium text-text hover:bg-surface-muted aria-[current=page]:bg-primary-soft aria-[current=page]:text-primary-soft-foreground"
>
{item}
</button>
</Drawer.Close>
))}
</nav>
</Drawer.Content>
</Drawer>
);
}
Tafsilotlar
size="lg" — buyurtma yoki mijoz ma'lumotlari uchun keng panel. Drawer.Footer kontent qisqa
bo'lsa ham panel tubida turadi.
'use client';
import { Button, Drawer } from '@akhatmirzo/ui';
const rows = [
['Mijoz', 'Sarvar Abduxoliqov'],
['Telefon', '+998 90 123 45 67'],
['Manzil', 'Toshkent, Chilonzor tumani, 9-mavze'],
["To'lov", 'Naqd, yetkazib berilganda'],
['Jami', "1 250 000 so'm"],
];
export default function DrawerDetails() {
return (
<Drawer>
<Drawer.Trigger asChild>
<Button>Buyurtma #1042</Button>
</Drawer.Trigger>
<Drawer.Content size="lg" title="Buyurtma #1042" description="21-oktabr, 14:32">
<dl className="m-0 grid grid-cols-[auto_1fr] gap-x-6 gap-y-3 text-sm">
{rows.map(([label, value]) => (
<div key={label} className="contents">
<dt className="text-text-muted">{label}</dt>
<dd className="m-0 font-medium text-text">{value}</dd>
</div>
))}
</dl>
<Drawer.Footer>
<Drawer.Close asChild>
<Button>Yopish</Button>
</Drawer.Close>
<Button variant="primary">Yetkazildi deb belgilash</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer>
);
}
Ichida Select va Combobox
Drawer.Content ichidagi Select, Combobox,
DropdownMenu va Popover paneli drawer ustida ochiladi
— qo'shimcha sozlash kerak emas.
Props
Drawer
Radix Dialog Root prop'larini qabul qiladi: open, defaultOpen, onOpenChange, modal.
Drawer.Content
Qolgan <div> atributlari, ref va Radix hodisalari (onEscapeKeyDown, onOpenAutoFocus ...)
panelga uzatiladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
titlemajburiy | ReactNode | — | Sarlavha — ekran o'quvchi uchun ham |
description | ReactNode | — | Sarlavha ostidagi izoh |
side | 'right' | 'left' | 'bottom' | 'top' | 'right' | Qaysi chetdan chiqadi |
size | 'sm' | 'md' | 'lg' | 'md' | 320 / 420 / 560px — kenglik yoki balandlik |
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) |
overlayClassName | string | — | Fon (overlay) uchun klass |
container | HTMLElement | null | — | Portal konteyneri (standart — document.body) |
labels | { close?: string } | { close: 'Yopish' } | ✕ tugmasining nomi |
Drawer.Trigger, Drawer.Close, Drawer.Footer
Drawer.Trigger va Drawer.Close — asChild bilan o'z elementingizni oladi. Drawer.Footer —
oddiy <div>: tugmalar o'ngga tekislanadi va panel tubiga yopishadi (mt-auto).
Data atributlari
| Atribut | Qayerda | Qiymat |
|---|---|---|
data-state | panel | open · closed |
data-side | panel | right · left · bottom · top |
data-size | panel | sm · md · lg |
Dastlabki fokusni o'zingiz tanlash uchun elementga data-autofocus qo'ying.
Klaviatura
| Tugma | Harakat |
|---|---|
Tab | Panel ichidagi keyingi element (fokus paneldan chiqmaydi) |
Shift + Tab | Oldingi element |
Esc | Panelni yopadi, fokus ochgan elementga qaytadi |
Accessibility
- Rol va nom. Panel —
role="dialog",aria-labelledbysarlavhaga,aria-describedbyizohga ulanadi (izoh bo'lmasa — qo'yilmaydi). - Fokus tuzog'i va scroll. Ochiq paytda fokus panel ichida aylanadi, sahifa scroll qilinmaydi va ekran o'quvchidan yashiriladi.
- Dastlabki fokus:
[data-autofocus]→ birinchi maydon → birinchi tugma (✕ dan tashqari) → panelning o'zi. - Fokusni qaytarish. Yopilgach fokus triggerga, trigger bo'lmasa — ochilishdan oldingi elementga qaytadi.
- Tutqich chizig'i (
side="bottom") faqat bezak —aria-hidden. - Kamroq harakat.
prefers-reduced-motionda siljish o'rniga shaffoflik animatsiyasi.