Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. Dashboard xaritasi
- 2.1 Qaysi qism — qaysi komponent
- 2.2 Bloklar: butun sahifa bir buyruqda
- 3. DropdownMenu: amallar menyusi
- 3.1 Beshta qism
- 3.2 shadcn qatlami: primitiv ustidagi o'ram
- 3.3 Bandlar turlari: belgilash, tanlash, ajratgich
- 3.4 Bepul keladigan klaviatura va a11y
- 4. Dashboard'ning boshqa g'ishtlari
- 4.1 Dialog va AlertDialog
- 4.2 Sheet — chetdan chiqadigan panel
- 4.3 Popover va Tooltip
- 4.4 Command — buyruqlar oynasi
- 4.5 Field — forma
- 4.6 DataTable va Chart
- 4.7 Sidebar
- 5. Vazifalar qadami: menyular — shadcn DropdownMenu (holat 03)
- 5.1 Nima o'zgaradi
- 5.2 ui/dropdown-menu.tsx
- 5.3 Ishlatilishi: statistika hisoboti
- 5.4 Testlar: Radix fokusni kechiktirib qo'yadi
- 5.5 Natija va narx
- 6. Ko'p uchraydigan xatolar
- 6.1 Tooltip Provider'siz
- 6.2 Testda click menyuni ochmaydi
- 6.3 Boshqalari
- 7. Mashqlar
- 1-mashq (oson): qaysi komponent?
- 2-mashq (o'rta): qo'lda yozilgan ro'yxatni DropdownMenu'ga
- 3-mashq (qiyin): «Ko'rinish» menyusi haqiqiy ro'yxat bilan
- 4-mashq: Vazifalar qadami
- 8. Real ishda
- Xulosa
- Manbalar
shadcn/ui komponentlari real ilovada: DropdownMenu, Dialog, Sheet, Popover, Form va DataTable
Qisqacha: Admin panel (dashboard) bir xil "g'ishtlar"dan yig'iladi. Bular: Sidebar, DropdownMenu, Dialog, Sheet, Popover, Command, Field (forma), DataTable va Chart. shadcn/ui ularning har birini loyihangizga fayl qilib ko'chiradi. Ostida xulqni Radix beradi, ko'rinishni Tailwind sinflari. Eng ko'p ishlatiladigani — DropdownMenu:
Root→Trigger→Portal→Content→Item. Band tanlansaonSelectchaqiriladi va menyu o'zi yopiladi.
Bu darsda
- Dashboard'ni shadcn/ui komponentlariga bo'lasiz va har birining ostida nima turganini ayta olasiz.
- Radix DropdownMenu'ni qismlari bilan yozasiz:
Trigger,Portal,Content,Item,onSelect. - shadcn'ning
dropdown-menu.tsxfayli primitivni qanday "o'rashini" tushunasiz va o'zingiz shunday o'ram yozasiz. - Belgilash (checkbox), tanlash (radio), ajratgich va sarlavhali menyu qurasiz.
vazifalardagi o'zMenyumizni shadcn DropdownMenu bilan almashtirasiz va narxini (+27.2 KB) o'lchab ko'rasiz.
Oldin bilishingiz kerak: shadcn/ui I: qanday ishlaydi va sozlash, Compound komponentlar va slot naqshi, Headless komponentlar va prop getters, Portallar, RHF II: Controller, watch va tahrirlash formasi.
1. Nega bu kerak?
Oldingi darsda shadcn/ui'ni sozladik: components.json, @/ alias, cn() va cva. Loyiha komponent qabul qilishga tayyor. Endi savol: qaysi komponentlar va ular real ilovada qanday birlashadi?
Jasur aka «Bahor» uchun admin panel so'radi. Chapda bo'limlar: buyurtmalar, menyu, xodimlar. O'rtada buyurtmalar jadvali, har qator oxirida «Amallar» tugmasi: ko'rish, tahrirlash, o'chirish. O'chirishdan oldin "Ishonchingiz komilmi?" oynasi. Tepada filtr va "yangi taom" formasi. Burchakda kunlik savdo grafigi.
Bularni noldan yozsak-chi? 18-qismda bitta ochiladigan menyuni o'zimiz yozgan edik: Compound komponentlar, Controlled va uncontrolled API va Headless komponentlar darslari — uch dars, 326 qator kod. U ishladi, lekin bitta menyu, xolos. Dashboard'da esa o'nga yaqin shunday komponent bor.
Hayotiy o'xshatish: yangi oshxona jihozlash. Har bir pichoq va qozonni temirchiga buyurtma qilish mumkin. Lekin aqlli oshpaz sinalgan to'plamni oladi, keyin qaysidir pichoqni o'z qo'liga moslab charxlaydi. shadcn/ui — ana shu to'plam. Farqi: pichoqlar sizga berib yuboriladi, ijaraga emas. Fayl sizniki, xohlaganingizcha o'zgartirasiz.
2. Dashboard xaritasi
2.1 Qaysi qism — qaysi komponent
Avval katta rasmni ko'raylik. Diagrammada «Bahor» admin paneli qismlari va ularning ostidagi shadcn komponentlari:
flowchart TB
P["Admin panel"] --> S["Sidebar: bo'limlar"]
P --> T["DataTable: buyurtmalar"]
P --> C["Chart: savdo"]
T --> D["DropdownMenu: qator amallari"]
D --> A["Dialog: o'chirishni tasdiqlash"]
D --> SH["Sheet: tahrirlash formasi"]
SH --> F["Field + React Hook Form"]
T --> PO["Popover: filtr"]
P --> K["Command: Ctrl+K qidiruv"]Diagrammaga qarang: komponentlar bir-birining ichida yashaydi. Qator menyusi tasdiq oynasini ochadi, yon oyna ichida forma turadi. Shuning uchun ular bitta oiladan bo'lgani qulay. Har birining ostida nima turadi:
| Komponent | Vazifasi | Ostida | To'liq dars |
|---|---|---|---|
| DropdownMenu | bosilganda ochiladigan amallar ro'yxati | Radix Menu | shu dars, Radix chuqur |
| Dialog, AlertDialog | e'tiborni to'liq oladigan oyna | Radix Dialog | Modal, drawer, popover va tooltip |
| Sheet | ekran chetidan chiqadigan panel | Radix Dialog | Modal, drawer… |
| Popover | tugma yonida ochiladigan kichik panel | Radix Popover | Modal, drawer… |
| Tooltip | ustiga kelganda qisqa izoh | Radix Tooltip | Modal, drawer… |
| Komponent | Vazifasi | Ostida | To'liq dars |
|---|---|---|---|
| Command | qidiruvli buyruqlar ro'yxati | cmdk | Command palette |
| Field (forma) | yorliq, maydon, xato matni | React Hook Form | RHF II |
| DataTable | saralash, filtr, sahifalash | TanStack Table | Jadvallar I |
| Chart | ustun, chiziq, doira grafiklar | Recharts | shu dars (qisqa) |
| Sidebar | yig'iladigan yon panel | o'z kodi | shu dars (qisqa) |
Bu darsda DropdownMenu'ni to'liq ochamiz — vazifalar dagi bugungi qadam aynan u. Qolganlari bilan qisqa tanishamiz.
2.2 Bloklar: butun sahifa bir buyruqda
shadcn'da komponentdan kattaroq birlik ham bor — blok (block). Blok — bir nechta komponentdan yig'ilgan tayyor sahifa yoki bo'lim. Masalan dashboard-01 — "yon panel, grafiklar va jadvalli dashboard". sidebar-01 dan sidebar-16 gacha — yon panelning 16 xil varianti, login-01 kabi kirish sahifalari ham bor.
npx shadcn@4.21.4 add dashboard-01Buyruq o'nlab faylni loyihangizga yozadi: sahifa, yon panel, jadval, grafik va ular ishlatadigan har bir komponent. Oldingi darsdagi qoida bu yerda ikki karra muhim. CLI qo'shgan har bir paketni va versiyasini tekshiring, ^ belgisini olib tashlang. Blok — boshlang'ich nuqta, tayyor mahsulot emas: ichidagi namuna ma'lumotlarni o'zingiznikiga almashtirasiz.
Maslahat: Blokni avval alohida bo'sh loyihada
addqiling va nima kelishini ko'ring. Keyin keragini qo'lda ko'chiring — kanondagidropdown-menu.tsxshunday ko'chirilgan.
Tekshirib ko'ring: Sheet bilan Dialog'ning ostida bir xil primitiv turibdi. Nega ular ikki xil komponent?
Javob
Xulq bir xil: ochilganda fokus ichkariga kiradi, Esc yopadi, orqa fon bosilmaydi. Farq — ko'rinishda va maqsadda. Dialog ekran o'rtasida qisqa savol beradi ("o'chirilsinmi?"). Sheet ekran chetidan chiqadi va ichida uzunroq ish bo'ladi (tahrirlash formasi, filtrlar). shadcn bitta Radix Dialog ustiga ikki xil o'ram yozgan — sheet.tsx da side props'i panelni qaysi chetdan chiqarishni aytadi.
3. DropdownMenu: amallar menyusi
3.1 Beshta qism
Ochiladigan menyu (dropdown menu) — tugma bosilganda paydo bo'ladigan amallar ro'yxati. Ro'yxatdan bitta amal tanlanadi va menyu yopiladi. Telefoningizdagi Telegram'da xabarni bosib turganda chiqadigan "Javob berish, Nusxalash, O'chirish" ro'yxati — xuddi shu.
Radix uni besh qismga bo'ladi. Bu Compound komponentlar darsidagi "Radix uslubidagi anatomiya"ning o'zi:
| Qism | Nima qiladi |
|---|---|
Root |
holatni saqlaydi (ochiq/yopiq), qismlarni Context bilan bog'laydi |
Trigger |
menyuni ochadigan tugma |
Portal |
ro'yxatni <body> oxiriga chizadi |
Content |
ro'yxatning o'zi: joylashuv, klaviatura |
Item |
bitta amal |
Bu qismlar Radix'ning primitivlari (primitives) deyiladi. Primitiv — stilsiz, lekin to'liq xulqli qurilish bo'lagi: klaviatura, fokus, ekran o'quvchi uchun atributlar — hammasi bor, rang va o'lcham yo'q.
Bir eslatma. Saytdagi natija oynasida Tailwind ishlamaydi — u yerda build bosqichi yo'q. Shuning uchun jonli misollarda oddiy CSS yozamiz. Loyihada xuddi shu narsa Tailwind sinflari bilan yoziladi.
«Bahor» buyurtmasi qatoridagi amallar menyusi:
import { useState } from "react";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
const css = `
.menu { min-width: 10rem; padding: 4px; background: #fff;
border: 1px solid #bbb; border-radius: 8px; color: #111; }
.item { padding: 8px 12px; border-radius: 6px; outline: none; }
.item[data-highlighted] { background: #1d4ed8; color: #fff; }
`;
export default function App() {
const [action, setAction] = useState("—");
return (
<div>
<style>{css}</style>
<p>Buyurtma №12 — 63 000 so'm</p>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>Amallar</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="menu" align="start">
<DropdownMenu.Item
className="item"
onSelect={() => setAction("Ko'rish")}
>
Ko'rish
</DropdownMenu.Item>
<DropdownMenu.Item
className="item"
onSelect={() => setAction("Tahrirlash")}
>
Tahrirlash
</DropdownMenu.Item>
<DropdownMenu.Item
className="item"
onSelect={() => setAction("O'chirish")}
>
O'chirish
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<p>Tanlangan amal: {action}</p>
</div>
);
}Ekranda («Amallar» → ↓ → ↓ → Enter):
Buyurtma №12 — 63 000 so'm
Amallar
Tanlangan amal: Tahrirlash«Ishga tushir» ni bosing, keyin «Amallar» ni. Ro'yxat ochiladi. Endi sichqonchani qo'yib, klaviaturani sinang: ↓ birinchi bandga, yana ↓ — «Tahrirlash» ga o'tadi, Enter tanlaydi. Menyu yopiladi, pastda amal nomi chiqadi. ↑ orqaga yuradi, Esc tanlovsiz yopadi. Bularning birortasini biz yozmadik.
| Qadam | Fokus | Ekranda |
|---|---|---|
| «Amallar» bosildi | ro'yxatning o'zida | ro'yxat ochildi |
| ↓ | «Ko'rish» | band ajratildi |
| ↓ | «Tahrirlash» | band ajratildi |
| Enter | «Amallar» tugmasiga qaytdi | ro'yxat yopildi, «Tanlangan amal: Tahrirlash» |
Kodni qatorma-qator o'qiymiz:
onSelect— band tanlanganda chaqiriladi: sichqoncha bilan bosilsa ham, Enter yoki Space bosilsa ham. Tanlovdan keyin menyu o'zi yopiladi va fokus «Amallar» tugmasiga qaytadi.data-highlighted— Radix hozir "ustida turgan" bandga qo'yadigan atribut. Sichqoncha bandga kelsa, fokus ham o'sha bandga o'tadi. Shuning uchun bitta qoida ikkalasini bezaydi,:hoveralohida kerak emas.modal={false}— menyu ochiq turganda sahifaning qolgan qismi ham ishlayveradi. Nega bunday qilganimizni Radix chuqur darsida o'lchov bilan ko'ramiz. Hozircha shuni biling: Radix'ning standart qiymati —true.align="start"— ro'yxat tugmaning chap chetiga tekislanadi. Standart —center.
Ochiq yoki yopiqlikni biz saqlamadik — useState yo'q, menyuni Radix o'zi boshqaradi. Bu 17-qismdagi uncontrolled rejim: defaultOpen bilan faqat boshlang'ich holatni berish mumkin ("boshida ochiq bo'lsin"), xuddi defaultValue kabi. Controlled rejim (open + onOpenChange) — keyingi darsda.
Diqqat: Ekranda ro'yxat tugma yonida, lekin DOM'da (DevTools Elements) u
<body>oxirida turadi. BuPortalning ishi. Joyini esa Floating UI hisoblaydi. Ota-elementningoverflow: hiddeni yokiz-indexi ro'yxatni endi kesmaydi — Portallar darsidagi "qamoq" muammosi shunday yechiladi.
3.2 shadcn qatlami: primitiv ustidagi o'ram
Har safar DropdownMenu.Portal, className="menu", align="start" yozish — takrorlash. shadcn shu takrorni bir faylga yig'adi. npx shadcn add dropdown-menu yozgan dropdown-menu.tsx aslida bir necha o'ram (wrapper) funksiyadan iborat. Har biri bitta Radix qismini oladi va unga loyihaning standartlarini qo'shadi.
Shunday o'ramni o'zimiz yozamiz. cn o'rnida clsx — oldingi darsdagi cn() ning asosi:
import { useState } from "react";
import { clsx } from "clsx";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
// --- ui/dropdown-menu.jsx: bir marta yoziladi ---
function DropdownMenu({ modal = false, ...props }) {
return <DropdownMenuPrimitive.Root modal={modal} {...props} />;
}
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
function DropdownMenuContent({ className, ...props }) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
align="start"
sideOffset={4}
className={clsx("menu", className)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuItem({
variant = "default",
className,
...props
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-variant={variant}
className={clsx("item", className)}
{...props}
/>
);
}
// --- ilova: endi qisqa ---
const css = `
.menu { padding: 4px; background: #fff; color: #111;
border: 1px solid #bbb; border-radius: 8px; }
.item { padding: 8px 12px; outline: none; }
.item[data-highlighted] { background: #1d4ed8; color: #fff; }
.item[data-variant="destructive"] { color: #b91c1c; }
`;
export default function App() {
const [log, setLog] = useState("—");
return (
<div>
<style>{css}</style>
<DropdownMenu>
<DropdownMenuTrigger>Taom: Osh</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onSelect={() => setLog("Arxivlash")}>
Arxivlash
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
onSelect={() => setLog("O'chirish")}
>
O'chirish
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<p>Oxirgi amal: {log}</p>
</div>
);
}Ekranda:
Taom: Osh
Oxirgi amal: ArxivlashPastki qismga qarang: ilova kodi endi Portal, joylashuv va sinflar haqida o'ylamaydi — ular o'ramga ko'chdi. shadcn fayli aynan shunday tuzilgan:
- Har qism — alohida funksiya, nomi hujjatdagidek (
DropdownMenuItem). Hujjat misollari loyihangizda o'zgarishsiz ishlaydi. data-slot— "bu qaysi qism" belgisi. CSS'da aniq qismni topish oson:[data-slot=dropdown-menu-item].variant— shadcn qo'shimchasi, Radix'da yo'q."destructive"(xavfli amal)data-variantatributiga aylanadi, stil atributga qarab beriladi....propsoxirida — chaqiruvchi har standartni bosib keta oladi (Komponent API'sini loyihalash qoidasi).
Maslahat: shadcn'ning to'liq
dropdown-menu.tsxfaylida 15 ta funksiya bor (Sub,Shortcut,CheckboxItem…). Kanon faqat 4 tasini qoldirgan — ishlatilmagan eksportniknipxato deydi (knip darsida). Fayl sizniki: keragini qoldiring, kerak bo'lsa yana CLI'dan oling.
3.3 Bandlar turlari: belgilash, tanlash, ajratgich
Menyu faqat "amal" emas. Ro'yxat sahifasida «Ko'rinish» menyusi bo'ladi: "faqat bajarilganlar" — belgilanadi yoki yo'q; tartib — "yangi" yoki "alifbo" dan biri. Buning uchun Radix'da maxsus bandlar bor:
| Qism | Nima | ARIA roli |
|---|---|---|
CheckboxItem |
yoqiladi/o'chiriladi (checked) |
menuitemcheckbox |
RadioGroup + RadioItem |
bir nechtadan bittasi (value) |
menuitemradio |
ItemIndicator |
belgi: yoki ● — faqat tanlanganda chiziladi | — |
Label, Separator, Group |
sarlavha, chiziq, guruh | — |
import { useState } from "react";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
const css = `
.menu { padding: 4px; background: #fff; color: #111;
border: 1px solid #bbb; border-radius: 8px; }
.item { padding: 6px 12px; outline: none; }
.item[data-highlighted] { background: #1d4ed8; color: #fff; }
.label { padding: 6px 12px; font-size: 12px; color: #555; }
.sep { height: 1px; margin: 4px 0; background: #ddd; }
`;
export default function App() {
const [onlyDone, setOnlyDone] = useState(false);
const [order, setOrder] = useState("yangi");
return (
<div>
<style>{css}</style>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>Ko'rinish</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className="menu" align="start">
<DropdownMenu.Label className="label">
Filtr
</DropdownMenu.Label>
<DropdownMenu.CheckboxItem
className="item"
checked={onlyDone}
onCheckedChange={setOnlyDone}
onSelect={(e) => e.preventDefault()}
>
<DropdownMenu.ItemIndicator>
✓{" "}
</DropdownMenu.ItemIndicator>
Faqat bajarilganlar
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator className="sep" />
<DropdownMenu.Label className="label">
Tartib
</DropdownMenu.Label>
<DropdownMenu.RadioGroup
value={order}
onValueChange={setOrder}
>
<DropdownMenu.RadioItem className="item" value="yangi">
<DropdownMenu.ItemIndicator>
●{" "}
</DropdownMenu.ItemIndicator>
Yangi
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem className="item" value="alifbo">
<DropdownMenu.ItemIndicator>
●{" "}
</DropdownMenu.ItemIndicator>
Alifbo
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<p>
Filtr: {onlyDone ? "bajarilganlar" : "hammasi"}, tartib:{" "}
{order}
</p>
</div>
);
}Ekranda («Ko'rinish» → «Faqat bajarilganlar» → «Alifbo»):
Ko'rinish
Filtr: bajarilganlar, tartib: alifbo«Faqat bajarilganlar» bosilganda menyu yopilmadi. Sabab — onSelect={(e) => e.preventDefault()}: preventDefault() "standart ishni qilma" deydi, bu yerda standart ish — yopish. Belgilash bandlarida bu qulay: odam ketma-ket bir nechtasini belgilaydi. «Alifbo» esa oddiy band kabi ishladi: tanlandi, menyu yopildi.
Holat (onlyDone, order) bizning useState da. Radix uni ko'rsatadi va o'zgarishini xabar qiladi: checked + onCheckedChange, value + onValueChange. Shu holatni saralash va filtrga bersangiz — tayyor «Ko'rinish» menyusi.
Yana ikki tur bor. Sub — ichma-ich menyu (Sub → SubTrigger → SubContent): «Eksport» ustida "CSV, JSON" ochiladi, → va ← kiradi va chiqadi. shadcn'dagi DropdownMenuShortcut esa shunchaki <span> ("E" yozuvi) — yorliqni o'zi ishlatmaydi (Command palette va klaviatura yorliqlari).
Tekshirib ko'ring: Belgilash bandidan
onSelect={(e) => e.preventDefault()}olib tashlansa, «Faqat bajarilganlar» bosilganda nima bo'ladi? Belgi qo'yiladimi?
Javob
Belgi qo'yiladi — onCheckedChange baribir chaqiriladi va onlyDone true bo'ladi. Lekin menyu yopiladi, chunki tanlovdan keyingi standart ish — yopish. Ikkinchi filtrni belgilash uchun odam menyuni qayta ochishi kerak. Bitta belgi bo'lsa — farqi kam. Beshta filtrli menyuda esa odam besh marta ochib-yopadi.
3.4 Bepul keladigan klaviatura va a11y
Headless komponentlar darsida useMenyu hookini yozganmiz: strelkalar, Home/End, harf bilan qidirish, Esc, tashqariga bosish. Radix'da hammasi bor va bundan ham ko'p:
| Klaviatura | Natija |
|---|---|
| Enter, Space yoki ↓ (tugmada) | ochiladi, fokus birinchi bandda |
| ↓ ↑ | keyingi / oldingi band; loop bo'lsa oxiridan boshiga |
| Home / End | birinchi / oxirgi band |
| harf | shu harf bilan boshlangan band |
| Esc | yopiladi, fokus tugmaga qaytadi |
| Tab | hech narsa: fokus bandda, menyu ochiq (sababi va APG bilan farqi — keyingi darsda) |
disabled band strelkada o'tkazib yuboriladi. Malika (ekran o'quvchi) uchun aria-haspopup, aria-expanded, aria-controls, role="menu" va role="menuitem" ni Radix o'zi qo'yadi. Fokus qayerga borishini keyingi darsda tekshiramiz.
4. Dashboard'ning boshqa g'ishtlari
Har biri bilan qisqa tanishamiz. Maqsad — "qachon qaysi biri" ni bilish. Chuqur o'rganish — jadvaldagi darslarda.
4.1 Dialog va AlertDialog
Dialog — sahifaning ustiga chiqadigan va e'tiborni to'liq oladigan oyna. Ochiq turganda orqadagi sahifa bosilmaydi, fokus oyna ichida aylanadi. «Bahor» da taomni o'chirishdan oldin so'rash:
import { useState } from "react";
import * as Dialog from "@radix-ui/react-dialog";
export default function App() {
const [deleted, setDeleted] = useState(false);
if (deleted) return <p>Osh menyudan olib tashlandi.</p>;
return (
<Dialog.Root>
<Dialog.Trigger>Osh'ni o'chirish</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Ishonchingiz komilmi?</Dialog.Title>
<Dialog.Description>
Osh menyudan olib tashlanadi.
</Dialog.Description>
<Dialog.Close>Bekor qilish</Dialog.Close>{" "}
<button onClick={() => setDeleted(true)}>
Ha, o'chirish
</button>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}Ekranda:
Osh menyudan olib tashlandi.Anatomiya tanish: Root, Trigger, Portal, Content. Yangilari — Overlay (orqa fonni qoplovchi qatlam), Title va Description (oyna nomi va izohi — ekran o'quvchi ochilganda shularni o'qiydi), Close (yopuvchi tugma). shadcn'da AlertDialog ham bor — xavfli amalni tasdiqlash uchun. Farqi: orqa fonni bosish uni yopmaydi, odam aniq javob berishi shart.
Bir halol eslatma. vazifalar dagi o'chirish tasdig'i Radix Dialog emas — native <dialog> (<dialog> va Popover API JS'dan darsida yozganmiz). Kanon Radix AlertDialog + sonner variantini o'lchadi: ilovaning xavfsizlik siyosati (CSP) ostida 4 ta buzilish. Sabab va o'lchov — Radix chuqur darsida.
4.2 Sheet — chetdan chiqadigan panel
Sheet — yuqorida aytganimizdek, Dialog'ning boshqa ko'rinishi. Jadvaldagi buyurtmani tahrirlash uchun o'ng chetdan forma chiqadi, jadval esa orqada ko'rinib turadi. shadcn'da shunday yoziladi (loyihada, Tailwind bilan):
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Tahrirlash</Button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Buyurtma №12</SheetTitle>
<SheetDescription>O'zgartiring va saqlang.</SheetDescription>
</SheetHeader>
<OrderForm />
</SheetContent>
</Sheet>side — top, right, bottom yoki left. asChild — Trigger o'z tugmasini chizmasin, bizning Button ni ishlatsin (Polymorphic komponentlar va asChild; Radix'da — keyingi darsda). Telefondagi pastki "tortma" uchun — Drawer (vaul kutubxonasi).
4.3 Popover va Tooltip
Popover — tugma yonida ochiladigan kichik panel. Ichida istalgan narsa bo'lishi mumkin: filtr, sana tanlash, rang tanlash. Menyudan farqi: Popover ichidagi narsalar amal emas, oddiy boshqaruv elementlari. Tab bilan ular bo'ylab yuriladi, strelkalar bilan emas.
import * as Popover from "@radix-ui/react-popover";
export default function App() {
return (
<Popover.Root>
<Popover.Trigger>Filtr</Popover.Trigger>
<Popover.Portal>
<Popover.Content sideOffset={4}>
<label>
<input type="checkbox" /> Faqat yetkazib berish
</label>{" "}
<Popover.Close>Yopish</Popover.Close>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}Ekranda:
Filtr
Faqat yetkazib berish YopishBu yerda bos("Filtr") oddiy bosish bilan ochdi. Popover tugmasi click hodisasida ochiladi. DropdownMenu tugmasi esa pointerdown da — sichqoncha tugmasi bosila boshlaganda, qo'yib yuborilishini kutmay. Sababi va testlardagi oqibati — «Ko'p uchraydigan xatolar» bo'limida.
Tooltip — ustiga kelganda yoki fokusda chiqadigan qisqa izoh, ikonka tugmalar uchun ("" nima qiladi?). Ichida tugma yoki havola bo'lmaydi, faqat matn. Radix talabi: hamma tooltiplar Tooltip.Provider ichida (u kechikishni boshqaradi). Unutilsa nima bo'lishi — xatolar bo'limida.
4.4 Command — buyruqlar oynasi
Command — qidiruv maydoni va filtrlangan ro'yxat: yozasiz, ro'yxat qisqaradi, ↓ va Enter bilan tanlaysiz. VS Code'dagi Ctrl+Shift+P ni eslang. shadcn'da u cmdk kutubxonasi ustiga qurilgan, Dialog ichida ochilsa — CommandDialog. vazifalar da u Ctrl+K bilan ochiladi va faqat birinchi ochilganda yuklanadi — Command palette va klaviatura yorliqlari darsida.
4.5 Field — forma
Formalar uchun shadcn'ning yangi yo'li — Field komponentlari (2025-yil oktabrda qo'shilgan): Field, FieldLabel, FieldDescription, FieldError. Ular ma'lumotni o'zi boshqarmaydi — React Hook Form'ning Controller i bilan ishlaydi. RHF II darsidagi Controller ni eslang:
<Controller
name="name"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Taom nomi</FieldLabel>
<Input
{...field}
id={field.name}
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>Hammasi tanish: aria-invalid — React'da accessibility darsidagi xato belgisi, htmlFor — yorliq bog'lanishi. data-invalid — stil uchun: yorliq va chegara qizil bo'ladi. Eski kodda <Form>, <FormField>, <FormMessage> ni ko'rasiz — shadcn'ning oldingi Form komponenti, u ham RHF ustida: FormField ≈ Controller, FormMessage ≈ FieldError.
4.6 DataTable va Chart
DataTable — shadcn'da komponent emas, qo'llanma. U TanStack Table ustida jadval yig'ishni bosqichma-bosqich ko'rsatadi: ustunlar, saralash, filtr, sahifalash, qator tanlash. Bitta tuzoq: shadcn qo'llanmasi TanStack Table v8 API'sida (useReactTable). Kanonda v9 (useTable) — Jadvallar I darsida. Qo'llanmani ko'chirsangiz, versiyani tekshiring.
Chart — Recharts grafiklari ustidagi ingichka qatlam: ChartContainer, ChartTooltip, ChartLegend. Ranglar CSS o'zgaruvchilarida (--color-savdo), qorong'i temada o'zi almashadi. Kanonda Chart yo'q: teg ulushlari oddiy CSS chiziqlari (Kutubxona turlari va module augmentation darsida). Yana bir sabab: chart.tsx dagi ChartStyle sahifaga <style> tegini o'zi yozadi, kanonning xavfsizlik siyosati esa bunday teglarni to'sadi. Chart'ni alohida o'lchamadik — sabab Radix menyuniki bilan bir xil, keyingi darsda ko'ramiz.
4.7 Sidebar
Sidebar — yig'iladigan yon panel: SidebarProvider (holat; telefonda Sheet'ga aylanadi), Sidebar, SidebarHeader, SidebarContent, SidebarGroup, SidebarFooter va SidebarTrigger. Yana compound naqsh. Odatda u blok bilan olinadi (sidebar-07 — ikonkalarga yig'iladigan panel). vazifalar da yon panel yo'q: sahifalar menyusi tepada (React Router I).
Tekshirib ko'ring: Uchta vaziyat: (a) jadval qatoridagi "…" tugmasi; (b) "Faqat yetkazib berish" belgisi va narx oralig'i surgichi; (c) "Hisobni o'chirasizmi?" Har biriga qaysi komponent?
Javob
(a) DropdownMenu — amallar ro'yxati, bittasi tanlanadi va menyu yopiladi. (b) Popover — ichida oddiy boshqaruv elementlari (checkbox, surgich), Tab bilan yuriladi; menyu bo'lsa, surgichni strelkalar bilan boshqarib bo'lmasdi — strelkalar bandlar bo'ylab yuradi. (c) AlertDialog — xavfli amal: orqa fonni bosish yopmaydi, odam aniq "Ha" yoki "Yo'q" deyishi kerak.
5. Vazifalar qadami: menyular — shadcn DropdownMenu (holat 03)
5.1 Nima o'zgaradi
vazifalar da ikkita menyu bor: statistika panelidagi «Hisobotni yuklab olish» (CSV yoki JSON) va tepada foydalanuvchi menyusi («Chiqish»). Ikkalasi 18-qismda yozilgan o'z Menyu mizda: Menyu.tsx (100 qator), useMenyu.ts (200) va useBoshqariladiganHolat.ts (26). Bugun ular shadcn DropdownMenu bilan almashadi, uchala fayl o'chiriladi.
Nega ishlab turgan kodni almashtiramiz? Ilovaga tooltip, buyruqlar oynasi va boshqa komponentlar kelyapti — har biriga klaviatura, fokus va ARIA'ni o'zimiz yozsak, dizayn tizimini noldan quramiz. Narxi ham bor, uni quyida o'lchaymiz.
git switch -c feat/dropdown-menu
npm i -E @radix-ui/react-dropdown-menu@2.1.25-E (--save-exact) — aniq versiya, ^ siz. CLI shadcn'ning umumiy radix-ui paketini qo'shardi. Kanon esa alohida @radix-ui/react-dropdown-menu ni oldi. Sababi: umumiy paket bilan tsc -b xato berdi, chunki uning turlari hamma primitivni tortadi, react-select ni ham (kanon o'lchovi):
node_modules/@radix-ui/react-select/dist/index.d.mts(108,11): error TS2320: Interface 'SelectPopperPositionProps' cannot simultaneously extend types 'Omit<PopperContentProps & RefAttributes<HTMLDivElement>, "ref">' and 'SelectPopperPrivateProps'.Tarjimasi: "SelectPopperPositionProps interfeysi ikki turni bir vaqtda kengaytira olmaydi" — Select'ning turlarida ichki ziddiyat. Biz Select ishlatmaymiz-ku, lekin tsc umumiy paketning hamma turlarini tekshiradi. Alohida paketda bu muammo yo'q, hajm esa bir xil: build baribir faqat ishlatilgan kodni oladi (tree shaking).
5.2 ui/dropdown-menu.tsx
CLI yozgan fayl (266 qator, 15 funksiya) qo'lda ko'chirildi va moslandi. Kod kanondan aynan; izohlar qisqartirilgan va telefonga sig'ishi uchun bo'lingan (boshidagi izoh asl faylda besh band):
// dropdown-menu.tsx — shadcn/ui'dan KO'CHIRILGAN (20/#03) va
// moslangan: alohida paket, 4 qism, bizning tokenlar,
// modal={false} (CSP), loop, band balandligi 44 px
import type { ComponentProps } from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { cn } from "@/ui/cn.ts";
import { portalJoyi } from "@/ui/portal-joyi.ts";
export function DropdownMenu({
modal = false,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return (
<DropdownMenuPrimitive.Root
data-slot="dropdown-menu"
modal={modal}
{...props}
/>
);
}
export function DropdownMenuTrigger(
props: ComponentProps<typeof DropdownMenuPrimitive.Trigger>,
) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
);
}
// Portal: ro'yxat <main> oxirida (portal-joyi.ts) — ota-elementning
// overflow va z-index'i uni kesmaydi (18/#10 dagi toast kabi).
// Joylashuvni (pastda/tepada, ekran chetida) Floating UI
// hisoblaydi — style prop orqali (CSSOM, CSP'ga mos)
export function DropdownMenuContent({
className,
align = "start",
sideOffset = 4,
loop = true,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal container={portalJoyi()}>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
align={align}
loop={loop}
className={cn(
"z-50 flex min-w-(--radix-dropdown-menu-trigger-width) flex-col rounded-burchak border border-chegara bg-yuza p-1 text-matn shadow-soya",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
// Band — div role="menuitem" (Radix): fokus strelkalar bilan
// ko'chadi, sichqoncha ustiga kelganda ham fokus shu bandga
// o'tadi — shuning uchun faqat focus: holati bezaladi
export function DropdownMenuItem({
className,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Item>) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
className={cn(
"flex min-h-bosish cursor-default items-center gap-2 rounded-burchak px-3 py-2 whitespace-nowrap outline-none select-none focus:bg-asosiy focus:text-asosiy-ustida data-disabled:opacity-50",
className,
)}
{...props}
/>
);
}Sinflar qatori 70 belgidan uzun — kanon kodi o'zgartirilmaydi. shadcn'dan farqlar:
ComponentProps<typeof DropdownMenuPrimitive.Root>— primitivning props turi to'liq (React + TS III). Radix'ga yangi props qo'shilsa, o'ram ham qabul qiladi.- Tokenlar:
bg-popover,bg-accento'rnida bizningbg-yuza,bg-asosiy(oldingi darsdagi@theme).dark:kerak emas — tokenlarlight-dark()bilan almashadi. min-w-(--radix-dropdown-menu-trigger-width)— Radix'ning CSS o'zgaruvchisi (tugma eni): ro'yxat tugmadan tor bo'lmaydi.min-h-bosish— band 44 px. shadcn'dah-7(28 px) — barmoqqa kichik (WCAG 2.5.5).loop = true— 18-qismdagiMenyumiz kabi oxiridan boshiga aylanadi.modal = falsevacontainer={portalJoyi()}— o'lchov bilan: modal menyu xavfsizlik siyosatini buzadi (1 buzilish),<body>dagi portal axe'da "landmark'dan tashqarida" chiqadi. Ikkalasi — Radix chuqur darsida jonli misol bilan.
portal-joyi.ts — bitta funksiya (kanon):
// portal-joyi.ts — Radix portallari qayerga chiziladi (20/#03).
// Sukut bo'yicha <body> oxiriga — <main> dan tashqarida. axe
// ("region") buni "landmark'dan tashqaridagi kontent" deydi.
// <main> ichida ham joylashuv buzilmaydi: Floating UI
// position: fixed bilan ekran koordinatasida joylaydi
export function portalJoyi(): HTMLElement | null {
return document.querySelector("main");
}5.3 Ishlatilishi: statistika hisoboti
BatafsilStatistika.tsx da o'zgarish — nomlar almashdi, tuzilish o'sha (kanon):
-import { Menyu } from "../../ui/Menyu.tsx";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "../../ui/dropdown-menu.tsx";
...
- <Menyu>
- <Menyu.Tugma className="hisobot-tugma">
+ {/* 20/#03: shadcn/ui DropdownMenu (Radix) — 18/#26 dagi o'z
+ Menyu'miz o'rniga. Klaviatura, fokus qaytishi, aria-* —
+ Radix'da; onSelect — band tanlanganda, menyu o'zi
+ yopiladi */}
+ <DropdownMenu>
+ <DropdownMenuTrigger className="hisobot-tugma">
Hisobotni yuklab olish
- </Menyu.Tugma>
- <Menyu.Royxat>
- <Menyu.Band
- onTanla={() =>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent>
+ <DropdownMenuItem
+ onSelect={() =>
faylniYuklabBer(
statistikaCsv(s),
"statistika.csv",Menyu.Tugma → DropdownMenuTrigger, Menyu.Royxat → DropdownMenuContent, Menyu.Band + onTanla → DropdownMenuItem + onSelect. Compound naqshni bilganimiz uchun ko'chish qatorma-qator bo'ldi. Foydalanuvchi menyusida yana bitta detal: ism sahifaning o'ng chetida, shuning uchun ro'yxat ham o'ngga tekislanadi:
- <Menyu.Royxat>
- <Menyu.Band onTanla={chiqish}>Chiqish</Menyu.Band>
- </Menyu.Royxat>
- </Menyu>
+ </DropdownMenuTrigger>
+ {/* Ism o'ng chetda — ro'yxat ham o'ng chetga tekislanadi */}
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem onSelect={chiqish}>
+ Chiqish
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>asos.css dan menyuning 20 qator CSS'i o'chdi — stil endi dropdown-menu.tsx dagi Tailwind sinflarida.
5.4 Testlar: Radix fokusni kechiktirib qo'yadi
Menyu.test.tsx dagi oltita testdan to'rttasi qoldi. «controlled» va «qism tashqarida — aniq xato» testlari o'chirildi: bu endi Radix'ning ishi, uni Radix o'z testlarida tekshiradi. Biz faqat bizga kerak xulqni tekshiramiz: modal emas, aylanadi, fokus qaytadi.
Ikki yangilik bor. Birinchisi — menyu click bilan emas, Enter bilan ochiladi (fireEvent.keyDown(tugma(), { key: "Enter" })). Ikkinchisi — fokusni kutish (kanon):
// Radix fokusni keyingi vazifada (setTimeout) qo'yadi — ochilganda
// ham, strelkalarda ham. Shuning uchun har tugmadan keyin kutamiz
const fokusKut = (matn: string) =>
waitFor(() => expect(fokusda()).toBe(matn));Bizning useMenyu fokusni darhol ko'chirardi. Radix esa uni setTimeout bilan, keyingi vazifada qo'yadi (Event loop darsidagi makrovazifa). Test ochilishi bilan fokusni o'qisa, u hali eski joyda. waitFor — shart bajarilguncha qayta-qayta tekshiradi. Xuddi shu sabab bilan kanonning 18-qism brauzer ssenariysiga keyinroq har tugmadan keyin 60 ms kutish qo'shildi: uch urinishdan birida fokus erta o'qilgan edi. Testlar chuqur — React Testing Library darsida.
git add -A
git commitCommit (kanon): feat: menyular — shadcn DropdownMenu (Radix, modal emas), portal <main> da.
5.5 Natija va narx
Tekshiruvlar (haqiqiy, holat 03):
npm test— node:testtests 198 · pass 198, VitestTests 65 passed (65)(67 edi — ikki test Radix'ga o'tdi).lint,format:check,tip,knip—exit 0. axe — 6 ko'rinishda 0 buzilish.- Brauzer: 159, 72 va 100 tekshiruv — hammasi o'tdi. 20-qismning yangi tekshiruvlaridan menyuga oidlari:
✅ menyu: ↓ — ochildi, fokus birinchi bandda (menuitem): ["CSV (Excel uchun)","menuitem"]
✅ menyu: aria-expanded va data-state (tugma, ro'yxat): ["true","open","open"]
✅ menyu: portal — <main> ichida, statistika panelidan tashqarida: [true,true]
✅ menyu: modal emas — fon aria-hidden emas, body'da style yo'q: [0,null]
✅ menyu: nomi — tugma matni (aria-labelledby): "Hisobotni yuklab olish"
✅ menyu: End — oxirgi band: "JSON"
✅ menyu: Esc — yopildi, fokus tugmada, data-state closed: [true,true,"closed"]
✅ menyu: tashqariga bosish — yopildi: false
✅ menyu: CSP buzilishi yo'q: []
✅ konsolda xato yo'q: []Endi narx. Birinchi yuklashdagi JavaScript (gzip):
- holat 02 — o'z Menyu151,9 KB
- holat 03 — Radix menyu+27.2 KB179,1 KB
Manba: O'lchandi: holat 02 va 03, gzip -9, natijalar/tekshiruv-02.txt va tekshiruv-03.txt, 2026-10-14
326 qator o'z kodimiz o'rniga +27.2 KB. Eng kattalari (03-paketlar.txt): @radix-ui/react-menu 4.6 KB, Floating UI (joylashuvni hisoblovchi kutubxona) 4.5 + 4.4 KB, @radix-ui/react-collection 3.4 KB. Qolgani — fokus, tashqariga bosish va portal uchun mayda paketlar.
Bu ko'pmi? Qaror: Radix menyu qoladi — evaziga ichma-ich menyu, ekran chetida joylashuvni o'zgartirish, sinalgan ARIA va keyingi komponentlar bilan bir xil "til". Lekin har kilobayt hisobda: keyingi og'ir kutubxonalar (buyruqlar oynasi, sudrab tartiblash) faqat kerak bo'lganda yuklanadi — lazy va code splitting qoidasi.
6. Ko'p uchraydigan xatolar
6.1 Tooltip Provider'siz
Tooltip'ni boshqa komponentlardek "o'zi" qo'ysangiz, ilova umuman chizilmaydi:
import * as Tooltip from "@radix-ui/react-tooltip";
export default function App() {
return (
<Tooltip.Root>
<Tooltip.Trigger>🗑</Tooltip.Trigger>
<Tooltip.Content>Vazifani o'chirish</Tooltip.Content>
</Tooltip.Root>
);
}Konsolda:
Uncaught Error: `Tooltip` must be used within `TooltipProvider`Tarjimasi: "Tooltip TooltipProvider ichida ishlatilishi kerak". Bu Compound komponentlar darsidagi "qism ota tashqarisida — aniq xato" himoyasining o'zi, faqat Radix'niki. Davosi — ilova ildizida bitta <Tooltip.Provider> (shadcn'da <TooltipProvider>). Har tooltipga alohida o'rash shart emas.
6.2 Testda click menyuni ochmaydi
Sardor Vitest'da yozdi: fireEvent.click(tugma), keyin getByRole("menu") — "topilmadi". Brauzerda esa menyu ochilyapti! Sabab: Radix DropdownMenu tugmasi pointerdown ni tinglaydi. fireEvent.click esa faqat click yuboradi, haqiqiy pointerdown → pointerup → click zanjirini emas. pointerdown da ochish qulay: odam bosib, bandga surib, qo'yib yuborsa — bitta harakatda tanlov.
Davosi — testda klaviatura: fireEvent.keyDown(tugma, { key: "Enter" }), kanondagidek. Ustiga test Bobur (faqat klaviatura) yo'lini ham tekshiradi.
6.3 Boshqalari
| Belgi | Sabab | Davosi |
|---|---|---|
| ro'yxat ota-element ichida kesilib qoladi | Portal siz Content |
Content ni Portal ichiga |
| belgilash bandi bosilganda menyu yopiladi | tanlovdan keyingi standart — yopish | onSelect={(e) => e.preventDefault()} |
onClick ishlaydi, lekin Enter bilan — har xil |
amal onClick da, mantiq ikki joyda |
amal — onSelect da |
Cannot find module 'radix-ui' (TS2307) |
CLI importni yozdi, paketni o'rnatmadi | shadcn I: npm i -E bilan o'rnating |
tsc -b — TS2320 (react-select) |
umumiy radix-ui paketining turlari |
alohida @radix-ui/react-* paketlar |
DataTable kodi ishlamaydi (useReactTable) |
qo'llanma v8, loyihada v9 | Jadvallar I |
7. Mashqlar
Mashqlar kurs/mashqlar/20/03-dropdown-menu/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi komponent?
Vaziyatga mos shadcn komponenti nomini yozing (DropdownMenu, Dialog, AlertDialog, Sheet, Popover, Tooltip, Command):
- Jadval qatoridagi "…" ostida "Ko'rish, Nusxalash, O'chirish" →
- Ikonka tugma ustiga kelganda "Yuklab olish" yozuvi →
- O'ng chetdan chiqadigan buyurtma tahrirlash formasi →
- Ctrl+K bosilganda hamma sahifa va taomlar bo'yicha qidiruv →
- "Hisobni butunlay o'chirasizmi?" — orqa fonni bosish yopmasin →
- Sana va narx oralig'i tanlanadigan kichik filtr paneli →
Yechim
- DropdownMenu — amallar ro'yxati: bittasi tanlanadi, menyu yopiladi, strelkalar ishlaydi.
- Tooltip — faqat matnli izoh, ustiga kelish yoki fokusda chiqadi.
- Sheet — Dialog xulqi, lekin ekran chetidan va uzunroq ish uchun.
- Command — qidiruv maydoni + filtrlangan ro'yxat (cmdk).
- AlertDialog — xavfli amal, aniq javob talab qiladi.
- Popover — ichida oddiy boshqaruv elementlari, Tab bilan yuriladi. Menyu bo'lsa, surgichni strelkalar bilan siljitib bo'lmasdi.
2-mashq (o'rta): qo'lda yozilgan ro'yxatni DropdownMenu'ga
Kodda «Ulashish» menyusi qo'lda yozilgan: useState bilan ochiladi, ichida oddiy tugmalar. Klaviatura, Esc va fokus qaytishi yo'q. Uni Radix DropdownMenu'ga o'tkazing:
- «Ulashish» —
Trigger, ro'yxat —PortalichidagiContent. - «Telegram» va «Havolani nusxalash» —
Item, «Instagram» —disabled(hali ulanmagan). - Oxirgi band oldidan
Separator, oxirgi band — «Shikoyat qilish». - Tanlangan amal pastda chiqsin:
Oxirgi amal: ….
Ishora: «Beshta qism» bo'limidagi misol. disabled band — xuddi <button disabled> kabi prop. Darsdagidek modal={false} qo'ying.
import { useState } from "react";
export default function App() {
const [open, setOpen] = useState(false);
const [last, setLast] = useState("—");
function choose(name) {
setLast(name);
setOpen(false);
}
return (
<div>
<button onClick={() => setOpen(!open)}>Ulashish</button>
{open && (
<div>
<button onClick={() => choose("Telegram")}>Telegram</button>
<button onClick={() => choose("Havola")}>
Havolani nusxalash
</button>
<button disabled>Instagram</button>
<hr />
<button onClick={() => choose("Shikoyat")}>
Shikoyat qilish
</button>
</div>
)}
<p>Oxirgi amal: {last}</p>
</div>
);
}Ekranda:
Ulashish
Oxirgi amal: TelegramYechim
import { useState } from "react";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
export default function App() {
const [last, setLast] = useState("—");
return (
<div>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>Ulashish</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content align="start" loop>
<DropdownMenu.Item onSelect={() => setLast("Telegram")}>
Telegram
</DropdownMenu.Item>
<DropdownMenu.Item onSelect={() => setLast("Havola")}>
Havolani nusxalash
</DropdownMenu.Item>
<DropdownMenu.Item disabled>Instagram</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={() => setLast("Shikoyat")}>
Shikoyat qilish
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<p>Oxirgi amal: {last}</p>
</div>
);
}Ekranda:
Ulashish
Oxirgi amal: TelegramEkran bir xil, farq — ichida. open state va choose kerak emas: ochish-yopishni Radix boshqaradi. Strelkalar (Instagram o'tkazib yuboriladi), Esc, tashqariga bosish va fokus qaytishi qo'shildi. Malika uchun — role="menu", menuitem va «Instagram» da aria-disabled.
3-mashq (qiyin): «Ko'rinish» menyusi haqiqiy ro'yxat bilan
«Bahor» taomlari ro'yxatiga «Ko'rinish» menyusini qo'shing:
- Taomlar: Osh 35 000, Lag'mon 28 000, Manti 30 000, Ko'k choy 5 000. Ro'yxat
<ul>da, har qator:Osh — 35000. CheckboxItem«Faqat 30 000 gacha» — belgilansa, narxi 30 000 dan oshiqlar yashiriladi. Bosilganda menyu yopilmasin.RadioGroup— tartib: «Nomi» (alifbo) yoki «Narxi» (arzonidan qimmatiga). Boshida — «Nomi».ItemIndicatorbilan va ● belgilar.
Ishora: «Bandlar turlari» bo'limidagi misol. Saralash uchun nusxa oling: [...items].sort(...) (sort va taqqoslash funksiyasi darsida). Satrlarni solishtirish — a.name.localeCompare(b.name).
Yechim
import { useState } from "react";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
const DISHES = [
{ name: "Osh", price: 35000 },
{ name: "Lag'mon", price: 28000 },
{ name: "Manti", price: 30000 },
{ name: "Ko'k choy", price: 5000 },
];
export default function App() {
const [cheap, setCheap] = useState(false);
const [sortBy, setSortBy] = useState("nomi");
const visible = DISHES.filter((d) => !cheap || d.price <= 30000);
const sorted = [...visible].sort((a, b) =>
sortBy === "nomi"
? a.name.localeCompare(b.name)
: a.price - b.price,
);
return (
<div>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>Ko'rinish</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content align="start">
<DropdownMenu.CheckboxItem
checked={cheap}
onCheckedChange={setCheap}
onSelect={(e) => e.preventDefault()}
>
<DropdownMenu.ItemIndicator>
✓{" "}
</DropdownMenu.ItemIndicator>
Faqat 30 000 gacha
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator />
<DropdownMenu.RadioGroup
value={sortBy}
onValueChange={setSortBy}
>
<DropdownMenu.RadioItem value="nomi">
<DropdownMenu.ItemIndicator>
●{" "}
</DropdownMenu.ItemIndicator>
Nomi
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="narxi">
<DropdownMenu.ItemIndicator>
●{" "}
</DropdownMenu.ItemIndicator>
Narxi
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<ul>
{sorted.map((d) => (
<li key={d.name}>
{d.name} — {d.price}
</li>
))}
</ul>
</div>
);
}Ekranda:
Ko'rinish
Ko'k choy — 5000
Lag'mon — 28000
Manti — 30000«Faqat 30 000 gacha» Osh'ni yashirdi, menyu ochiq qoldi. «Narxi» ro'yxatni arzonidan tizdi va menyuni yopdi. Ro'yxat holatdan hisoblanadi (visible, sorted) — alohida "saralangan ro'yxat" state'i yo'q (State strukturasini loyihalash qoidasi).
4-mashq: Vazifalar qadami
feat/dropdown-menu branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni qiling: paketni -E bilan o'rnating, ui/dropdown-menu.tsx va ui/portal-joyi.ts ni yozing, ikki menyuni ko'chiring, eski uch faylni va CSS'ni o'chiring, Menyu.test.tsx ni yangilang. Keyin commit.
Yechim
Tekshiruv ro'yxati:
npm test— node 198, Vitest 65. Menyu testi "topilmadi" bilan yiqilsa —clicko'rnidakeyDown EntervafokusKut.npm run tip— 0 xato. TS2320 chiqsa — umumiyradix-uio'rnida alohida paket.- «Hisobotni yuklab olish» → ↓ → CSV. Esc — fokus tugmada.
- DevTools Elements: menyu ochiq turganda ro'yxat
<main>ichida,<body>dastyleatributi yo'q. npm run build— birinchi yuklash ~179 KB gzip (oldin ~152).
8. Real ishda
- Admin panellar va SaaS — ko'p mahsulot interfeysi shu "g'ishtlar"dan: yon panel, qator menyusi, yon oyna, buyruqlar oynasi. Bloklar bilan skelet bir kunda tayyor.
- Vakansiyada "shadcn/ui" — komponent faylini o'zgartira oladigan odam kutiladi, faqat
addqiladigan emas. Har o'zgarish kanondagidek fayl boshida izoh bilan yozilsin — kod ko'rigida birinchi savol "nega?". - Intervyu savollari: "shadcn/ui'ning kutubxonadan farqi nima?", "DropdownMenu va Popover qachon qaysi biri?", "
onSelectvaonClickfarqi?", "nega testda menyuclickbilan ochilmaydi?".
Xulosa
- Dashboard — tayyor g'ishtlar: Sidebar, DropdownMenu, Dialog, Sheet, Popover, Tooltip, Command, Field, DataTable, Chart. Bloklar — ulardan yig'ilgan tayyor sahifa.
- DropdownMenu =
Root→Trigger→Portal→Content→Item. Amal —onSelectda, menyu o'zi yopiladi;e.preventDefault()— ochiq qoldiradi. CheckboxItem,RadioGroup/RadioItem,ItemIndicator,Label,Separator,Sub— menyuning boshqa bandlari. Holat sizning state'ingizda.- shadcn fayli — primitiv ustidagi ingichka o'ram:
data-slot, standart props, sinflar. Fayl sizniki: kanon uni tokenlar, 44 px,modal={false}va<main>dagi portal bilan moslagan. - Kutubxona bepul emas: Radix menyu +27.2 KB. Evaziga sinalgan klaviatura va ARIA, va keyingi komponentlar uchun bir xil til.
Keyingi dars: Radix Primitives chuqur — asChild bilan o'z Tugma mizni trigger qilish, data-state bilan stil, controlled menyu, fokus yo'li, portal va nega modal={false} (CSP o'lchovi bilan); Radix, Base UI va React Aria taqqoslovi.
Manbalar
- shadcn/ui: Dropdown Menu, Dialog, Sheet, Popover, Sidebar, Chart, Blocks — ui.shadcn.com/docs (2026-10, CLI 4.21.4)
- shadcn/ui: Forms — React Hook Form va Field — ui.shadcn.com/docs/forms/react-hook-form
- Radix Primitives: Dropdown Menu — radix-ui.com/primitives/docs/components/dropdown-menu (2.1.25)
- WAI-ARIA APG: Menu Button Pattern — w3.org/WAI/ARIA/apg/patterns/menu-button
- Kanon:
vazifalarholat 03,natijalar/tekshiruv-03.txt,03-paketlar.txt,03-radix-ui-umumiy-paket.txt(2026-10-14)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!