Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Xarita: to'rt qatlam
- 3. Modal: native <dialog> va Radix Dialog
- 3.1 Native <dialog> — vazifalar dagi yo'l
- 3.2 Radix Dialog: nimasi bor, nimasi qo'shimcha
- 3.3 AlertDialog
- 3.4 Skroll bloklash — CSP'ga mos yo'l
- 4. Drawer: vaul
- 5. Popover
- 5.1 Radix Popover: simlarni o'qiymiz
- 5.2 Native Popover API
- 6. Tooltip
- 6.1 Uch qoida
- 6.2 Radix Tooltip: Bobur yo'li
- 7. Floating UI: offset, flip, shift
- 7.1 Joylashuv nega qiyin
- 7.2 Floating UI to'g'ridan va Radix orqali
- 8. URL bilan modal
- 9. Vazifalar qadami: Radix tooltip (holat 11 boshlanadi)
- 9.1 Nima va nega
- 9.2 ui/tooltip.tsx
- 9.3 Provider — Qobiq.tsx da
- 9.4 Tekshiruv (holat 11, kanon e2e-20)
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): qaysi qatlam?
- 2-mashq (o'rta): «Qo'llash» popover'ni yopsin
- 3-mashq (qiyin): havola bilan kirilgan modal
- 4-mashq: Vazifalar qadami
- 12. Real ishda
- Xulosa
- Manbalar
React'da modal, drawer, popover va tooltip: qatlamli UI va Floating UI
Qisqacha: Sahifa ustiga chiqadigan to'rt xil qatlam bor. Modal (dialog) — sahifani to'xtatib, javob kutadi. Drawer — ekran chetidan chiqadigan modal (telefonda pastdan). Popover — tugma yonida ochiladigan, sahifani to'xtatmaydigan panel. Tooltip — fokus yoki sichqonchada chiqadigan qisqa izoh, faqat qo'shimcha ma'lumot. Joylashuvni Floating UI hisoblaydi:
offset(oraliq),flip(joy bo'lmasa ag'darish),shift(ekrandan chiqmaslik).vazifalarda modal — native<dialog>, tooltip — Radix.
Bu darsda
- To'rt qatlam turini xulqi bo'yicha farqlaysiz va har vaziyatga to'g'risini tanlaysiz.
- Native
<dialog>va Radix Dialog/AlertDialog'ni taqqoslaysiz, skroll bloklashni CSP'ga mos yo'l bilan qilasiz. - vaul drawer'ni va uning narxini bilasiz.
- Radix Popover va Tooltip'ning a11y "simlarini" jonli ko'rasiz:
aria-expanded,role="tooltip",aria-describedby. - Floating UI'ning
offset,flip,shiftg'oyasini va Radix'dagi mos props'larni bilasiz. - Modalni URL bilan bog'laysiz — «Orqaga» uni yopadi.
vazifalarga Radix tooltip qo'shishni boshlaysiz (holat 11).
Oldin bilishingiz kerak: <dialog> va Popover API JS'dan, Modal oyna va skroll bloklash, Tooltip, dropdown va toast, Radix Primitives chuqur, Toast bildirishnomalar.
1. Nega bu kerak?
«Bahor» panelida to'rtta yangi talab paydo bo'ldi:
- Taomni o'chirishdan oldin so'rash — "Osh menyudan olinsinmi?"
- Telefonda filtrlar pastdan chiqadigan panelda bo'lsin.
- "Sana" tugmasi yonida kichik kalendar ochilsin.
- Faqat ikonkali tugmalar ustida qisqa izoh chiqsin.
To'rttasi ham "sahifa ustida suzib yuradigan narsa". Sardor hammasini bitta Modal komponenti bilan qilmoqchi edi. Lekin ular xulqi bilan farq qiladi: kimdir sahifani to'xtatadi, kimdir yo'q; kimdir fokusni ichiga oladi, kimdir oldirmaydi. Noto'g'ri tur tanlansa, Bobur (faqat klaviatura) qamalib qoladi yoki Malika (ekran o'quvchi) muhim savolni eshitmaydi.
Hayotiy o'xshatish: oshxonadagi to'rt xil "to'xtash". Kassir "to'lovni tasdiqlaysizmi?" deydi — siz javob bermaguncha navbat to'xtaydi (modal). Menyu stoli chetidan chiqadi (drawer). Ofitsiant yoningizda bir lahza turib, ziravor tanlatadi — boshqa ishni to'xtatmaydi (popover). Taom yonidagi kichik yozuv "achchiq" (tooltip) — o'qisangiz ham, o'qimasangiz ham bo'ladi.
2. Xarita: to'rt qatlam
| Sahifani to'xtatadimi | Fokus | Qanday yopiladi | |
|---|---|---|---|
| Modal (dialog) | ha — fon "o'lik" | ichkariga, tuzoq | Esc, tugma; fon bosish — ixtiyoriy |
| Drawer | ha (modal turi) | ichkariga, tuzoq | Esc, surish, fon |
| Popover | yo'q | ichkariga, tuzoqsiz | Esc, tashqariga bosish |
| Tooltip | yo'q | ko'chmaydi | Esc, fokus/sichqoncha ketganda |
Va ularning rollari: modal — role="dialog" (tasdiq oynasi — alertdialog), popover — Radix'da dialog (modal emas), tooltip — role="tooltip". Menyu (role="menu") — alohida tur, shadcn/ui II va Radix chuqur darslarida o'tildi.
Tanlash yo'li:
flowchart TD
A{"Javob kerakmi /<br/>ish to'xtashi kerakmi?"} -->|ha| B
B{"Telefonda<br/>chetdan chiqsinmi?"}
B -->|ha| D["Drawer"]
B -->|yo'q| M["Modal (dialog)"]
A -->|yo'q| C{"Ichida bosiladigan<br/>narsa bormi?"}
C -->|ha| P["Popover"]
C -->|yo'q, faqat matn| T["Tooltip"]Nimaga qarang: tooltip va popover orasidagi chegara — "ichida tugma yoki maydon bormi?". Tooltip ichida bosiladigan narsa bo'lmaydi: u fokus olmaydi, Bobur unga yetib bora olmaydi.
3. Modal: native <dialog> va Radix Dialog
3.1 Native <dialog> — vazifalar dagi yo'l
<dialog> va Popover API darsida brauzerning o'z modalini yozganmiz: showModal() fonni o'chiradi (inert), fokusni ichkariga oladi, Esc yopadi, yopilganda fokus qaytadi. vazifalar dagi o'chirish tasdig'i 13-qismdan beri shunday.
React'da u ref orqali boshqariladi:
import { useRef, useState } from "react";
export default function App() {
const dialog = useRef(null);
const [result, setResult] = useState("—");
return (
<div>
<button onClick={() => dialog.current.showModal()}>
Osh'ni o'chirish
</button>
<p>Javob: {result}</p>
<dialog
ref={dialog}
aria-labelledby="sarlavha"
onClose={(e) => setResult(e.currentTarget.returnValue)}
>
<h2 id="sarlavha">Osh menyudan olinsinmi?</h2>
<form method="dialog">
<button value="yoq">Bekor qilish</button>{" "}
<button value="ha">O'chirish</button>
</form>
</dialog>
</div>
);
}Bu blok tekshiruvchida ishga tushirilmaydi: u ishlatadigan jsdom'da showModal yo'q (TypeError: ref.current.showModal is not a function). Saytdagi natija oynasi esa haqiqiy brauzer — «Ishga tushir» bilan sinang. «O'chirish» ni bosing: «Javob: ha». Esc bossangiz — «Javob: » bo'sh qoladi (returnValue o'rnatilmadi).
form method="dialog"— forma yuborilganda oyna yopiladi, bosilgan tugmaningvaluesireturnValuega yoziladi. JavaScript'siz.onClose— har yopilishda (tugma, Esc) bitta hodisa.aria-labelledby— oyna nomi sarlavhadan.
3.2 Radix Dialog: nimasi bor, nimasi qo'shimcha
Radix Dialog'ning anatomiyasini UI kutubxonalar xaritasi va shadcn/ui II darslarida ko'rdik: Root, Trigger, Portal, Overlay, Content, Title, Description, Close. Native bilan taqqoslash:
native <dialog> |
Radix Dialog | |
|---|---|---|
| Fon "o'lik" | showModal() — top layer, inert |
aria-hidden qo'shnilarga + pointer-events: none |
| Fokus tuzog'i | brauzerda | Radix kodi |
| Skroll bloklash | yo'q | bor (react-remove-scroll, <style> tegi) |
| Animatsiya (chiqish) | @starting-style va allow-discrete |
data-state + forceMount |
CSP style-src 'self' |
0 buzilish | 1 buzilish (kanon kutubxonalar.txt) |
Natija oynasida Radix Dialog bor — skroll bloklash <style> ini UI kutubxonalar xaritasi darsida o'lchagan edik (yangi <style>: 1). Kanon tajribasida alohida Radix Dialog — faqat React'ga nisbatan +13.6 KB va 1 CSP buzilishi.
Radix'da oyna nomi va izohi — Title va Description. Izoh kerak bo'lmasa, Radix hujjati aria-describedby={undefined} ni Content ga berishni aytadi — aks holda dev rejimda ogohlantirish chiqadi. (Natija oynasidagi Radix Dialog 1.2.0 bu ogohlantirishni konsolga chiqarmaydi — qoidani hujjatdan bilamiz.)
3.3 AlertDialog
AlertDialog — xavfli amalni tasdiqlash uchun alohida primitiv (@radix-ui/react-alert-dialog, natija oynasida yo'q). Dialog'dan uch farqi:
role="alertdialog"— ekran o'quvchi uni qat'iyroq e'lon qiladi;- fonni bosish uni yopmaydi — aniq javob kerak;
- tugmalar —
CancelvaAction; ochilganda fokusCancelda (xavfsiz tanlov).
<AlertDialog.Root>
<AlertDialog.Trigger>Menyuni tozalash</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialog.Content>
<AlertDialog.Title>
Butun menyu o'chirilsinmi?
</AlertDialog.Title>
<AlertDialog.Description>
12 taom va ularning narxlari o'chadi.
</AlertDialog.Description>
<AlertDialog.Cancel>Bekor qilish</AlertDialog.Cancel>
<AlertDialog.Action onClick={tozala}>
Tozalash
</AlertDialog.Action>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>Esc esa Radix AlertDialog'ni yopadi (kanon tajribasida shadcn varianti: escdanKeyin: oyna false). HeroUI'da standart bo'yicha — yo'q (boshqa kitlar).
3.4 Skroll bloklash — CSP'ga mos yo'l
Native <dialog> ochiq turganda orqadagi sahifa aylanaveradi. Modal oyna va skroll bloklash darsida buni sof CSS bilan hal qilgan edik:
html:has(dialog:modal) {
overflow: hidden;
scrollbar-gutter: stable;
}
dialog {
overscroll-behavior: contain;
}Bu — oddiy CSS faylda, JavaScript <style> yasamaydi. Ya'ni style-src 'self' bilan to'liq mos. Radix ham, React Aria ham skroll'ni JavaScript bilan bloklaydi: Radix <style> tegi bilan (CSP buzadi), React Aria esa kompyuter brauzerlarida <html> ning style xususiyatini o'zgartiradi (CSSOM — CSP ruxsat beradi). Bir muammo — uch yechim, CSP bilan munosabati har xil.
4. Drawer: vaul
Drawer (tortma) — ekran chetidan chiqadigan modal. Telefonda pastdan chiqib, barmoq bilan pastga surib yopiladi. shadcn'da Drawer — vaul kutubxonasi ustida.
import { Drawer } from "vaul";
<Drawer.Root snapPoints={[0.4, 1]}>
<Drawer.Trigger>Filtrlar</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Title>Filtrlar</Drawer.Title>
{/* filtr formasi */}
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>snapPoints — panel to'xtaydigan balandliklar: 40% va to'liq ekran. Anatomiya Radix Dialog'niki — chunki vaul uning ustiga qurilgan: vaul 1.1.2 ning yagona bog'liqligi — @radix-ui/react-dialog. Ustiga surish (drag) xulqi va animatsiya qo'shilgan.
Narxi (kanon kutubxonalar.txt, faqat React'ga nisbatan): +21.4 KB va 3 ta CSP buzilishi — Radix Dialog'ning <style> i va vaul'ning o'z CSS'i (manba kodida document.createElement('style') bor). Yana bir fakt: npm'da vaul'ning oxirgi relizi — 1.1.2, 2024-12-14. Kutubxona tanlashda "kim va qachon yangilaydi?" savolini eslang (UI kutubxonalar xaritasi dagi 7-savol).
CSP'li ilovada muqobil: native <dialog> + Modal oyna va skroll bloklash darsidagi CSS "sheet" — pastda joylashgan, @starting-style bilan pastdan chiqadi. Surib yopish bo'lmaydi, lekin Esc, fon va tugma ishlaydi.
5. Popover
5.1 Radix Popover: simlarni o'qiymiz
Popover — tugmaga bog'langan, sahifani to'xtatmaydigan panel: sana tanlash, filtr, rang tanlash. Ichida tugma va maydonlar bo'ladi — Tab bilan yuriladi. shadcn/ui II darsida ochib ko'rgan edik. Endi atributlarini o'qiymiz:
import * as Popover from "@radix-ui/react-popover";
function inspect(open) {
setTimeout(() => {
const btn = document.querySelector("button");
const panel = document.querySelector("[data-side]");
console.log("ochiq:", open);
const attr = (name) => btn.getAttribute(name);
console.log("tugma aria-haspopup:", attr("aria-haspopup"));
console.log("tugma aria-expanded:", attr("aria-expanded"));
console.log("panel role:", panel.getAttribute("role"));
console.log("data-side:", panel.dataset.side);
console.log("data-align:", panel.dataset.align);
}, 100);
}
export default function App() {
return (
<Popover.Root onOpenChange={inspect}>
<Popover.Trigger>Sana</Popover.Trigger>
<Popover.Portal>
<Popover.Content sideOffset={8}>
<label>
Kun: <input type="date" />
</label>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}Konsolda:
ochiq: true
tugma aria-haspopup: dialog
tugma aria-expanded: true
panel role: dialog
data-side: bottom
data-align: centeraria-haspopup="dialog"— tugma "dialog ochadi" deydi. Menyudamenuedi.role="dialog"— panel ham dialog, lekin modal emas: fon tirik,aria-hiddenqo'yilmaydi. Shuning uchun popover ichida sarlavha bo'lmasa ham ishlaydi, lekinaria-labelbersangiz, Malika "Sana, dialog" ni eshitadi.data-side="bottom",data-align="center"— standart joylashuv: tugma ostida, markazda.sideOffset={8}— tugmadan 8 piksel pastroqda.
Popover'da modal prop ham bor (standart false). modal={true} — fon "o'ladi", fokus tuzoqqa tushadi va skroll bloklanadi — Radix chuqur dagi menyu kabi <style> bilan. Kanon tajribasida Popover (modal siz) — +24.0 KB, CSP 0.
5.2 Native Popover API
Brauzerda ham popover bor: popover atributi va popovertarget (W-53, C-116 darslarida). Top layer'da ochiladi, Esc va tashqariga bosish bilan yopiladi — JavaScript'siz. Ikkita narsa yetishmaydi: tugmaga joylashish (CSS anchor positioning bilan qilinadi, lekin barcha brauzerlarda bir xil emas) va ochilganda fokusni ichkariga olib o'tish. Radix Popover ikkalasini ham beradi — Floating UI orqali.
6. Tooltip
6.1 Uch qoida
Tooltip, dropdown va toast darsida WCAG 1.4.13 ning uch talabini ko'rgan edik — sichqoncha yoki fokusda chiqadigan kontent:
- Yopiladigan (dismissible) — Esc bilan, sichqonchani qimirlatmasdan.
- Ustiga o'tsa bo'ladigan (hoverable) — sichqoncha tooltip ustiga o'tsa, u yo'qolmaydi.
- Turadigan (persistent) — odam o'zi ketmaguncha yo'qolmaydi.
React'da yana bitta qoida qo'shiladi: tooltip — faqat qo'shimcha. Telefonda hover yo'q, sensorli ekranda tooltip umuman chiqmaydi. Shuning uchun muhim ma'lumot tugmaning o'zida (matn yoki aria-label), tooltip'da emas. Kanon ui/tooltip.tsx izohi aynan shuni aytadi: "Tooltip — faqat QO'SHIMCHA ma'lumot (klaviatura yorlig'i)… telefonda (bosish) — yo'q. Shuning uchun muhim matn tugmaning o'zida yoziladi".
6.2 Radix Tooltip: Bobur yo'li
import * as Tooltip from "@radix-ui/react-tooltip";
function log(label) {
setTimeout(() => {
const btn = document.querySelector("button");
const id = btn.getAttribute("aria-describedby");
const tip = id && document.getElementById(id);
console.log(label, "| data-state:", btn.dataset.state);
console.log(
" aria-describedby →",
tip ? `${tip.getAttribute("role")}: ${tip.textContent}` : null,
);
console.log(" fokus:", document.activeElement.textContent);
}, 50);
}
export default function App() {
return (
<Tooltip.Provider delayDuration={300}>
<Tooltip.Root
onOpenChange={(open) => log(open ? "ochildi" : "yopildi")}
>
<Tooltip.Trigger>Buyruqlar</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content sideOffset={4}>
Tezkor ochish: Ctrl+K
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
}Konsolda:
ochildi | data-state: instant-open
aria-describedby → tooltip: Tezkor ochish: Ctrl+K
fokus: Buyruqlar
yopildi | data-state: closed
aria-describedby → null
fokus: BuyruqlarNatija oynasini bosing va klaviaturada Tab, keyin Esc ni bosing:
| Qadam | Fokus | Tooltip |
|---|---|---|
| Tab | «Buyruqlar» tugmasi | darhol ochildi (instant-open) |
| Esc | «Buyruqlar» — joyida | yopildi |
Uch narsani o'qiymiz:
instant-open— klaviatura fokusida tooltip kechikishsiz ochildi.delayDuration={300}faqat sichqoncha uchun: hover'da holatdelayed-openbo'ladi. Tasodifiy sichqoncha harakati tooltip'larni "chaqnatmasin", Bobur esa kutib o'tirmasin.aria-describedby → tooltip— tugma ochiq tooltip matniga bog'landi, urole="tooltip". Malika "Buyruqlar, tugma, Tezkor ochish: Ctrl+K" ni eshitadi: nom — tugmadan, izoh — tooltip'dan.- Esc — fokus joyida. Tooltip yopildi, fokus tugmada qoldi. Fokus hech qachon tooltip ichiga kirmaydi — u bosilmaydi.
Tooltip.Provider — barcha tooltiplarning umumiy sozlamasi: delayDuration va skipDelayDuration (bir tooltip ochiq bo'lsa, qo'shnisi kechikishsiz ochiladi — asboblar paneli bo'ylab yurganda qulay). Provider'siz Tooltip — xato (shadcn/ui II dagi «Tooltip Provider'siz»).
Tekshirib ko'ring: Sardor tooltip ichiga «Batafsil» havolasini qo'ydi. Nima noto'g'ri?
Javob
Tooltip fokus olmaydi: Bobur Tab bossa, fokus tugmadan keyingi elementga o'tadi va tooltip yopiladi — havolaga yetib bo'lmaydi. Sichqoncha bilan ham qiyin: tooltip harakatga sezgir, ko'p hollarda ustiga o'tish paytida yopiladi. Telefonda esa umuman ko'rinmaydi. Ichida bosiladigan narsa bo'lsa — bu popover («Xarita: to'rt qatlam» bo'limidagi daraxt).
7. Floating UI: offset, flip, shift
7.1 Joylashuv nega qiyin
Popover yoki tooltip tugma yonida turishi kerak. Lekin tugma ekranning pastki chetida bo'lsa-chi? Pastga ochilgan panel ekrandan chiqib ketadi. O'ng chetda bo'lsa — kesiladi. Sahifa aylantirilsa — panel tugmaga ergashishi kerak.
Bu hisob-kitobni Floating UI kutubxonasi qiladi (Anchor positioning va C-116 darslarida tilga olingan). Radix, shadcn, Mantine va boshqalar ichida aynan shu. Ishlash tartibi — middleware (oraliq bosqichlar) zanjiri:
flowchart LR
A["Boshlang'ich joy<br/>placement: bottom"] --> B["offset<br/>8 px nari"]
B --> C["flip<br/>joy yo'qmi? → top"]
C --> D["shift<br/>chetdan chiqmasin"]
D --> E["size<br/>bo'yini cheklash"]
E --> F["x, y → style"]offset— tugmadan oraliq.flip— tanlangan tomonda joy yo'q bo'lsa, qarama-qarshi tomonga ag'daradi (pastdan tepaga).shift— panel ekran chetidan chiqib ketsa, uni o'q bo'ylab suradi.size— mavjud balandlikka qarab panelni kichraytirish (uzun ro'yxatlar uchun).autoUpdate— skroll, o'lcham o'zgarishida joyni qayta hisoblash.
Nimaga qarang: tartib muhim — avval ag'darish, keyin surish. Oxirida natija — ikki raqam (x, y), ular style xususiyatiga yoziladi. Bu CSSOM, <style> tegi emas — shuning uchun Floating UI CSP bilan chiqishadi (Radix chuqur).
7.2 Floating UI to'g'ridan va Radix orqali
Kutubxonaning o'zi (@floating-ui/react 0.27.20, natija oynasida yo'q):
import {
useFloating, offset, flip, shift, autoUpdate,
} from "@floating-ui/react";
const { refs, floatingStyles, placement } = useFloating({
placement: "bottom",
middleware: [offset(8), flip(), shift({ padding: 8 })],
whileElementsMounted: autoUpdate,
});
<button ref={refs.setReference}>Sana</button>
<div ref={refs.setFloating} style={floatingStyles}>…</div>placement — haqiqiy joy (flip dan keyin "top" bo'lishi mumkin). Radix'da xuddi shu narsalar props bo'lib keladi:
| Floating UI | Radix Content prop'i |
DOM'da |
|---|---|---|
placement |
side, align |
data-side, data-align |
offset(8) |
sideOffset={8} |
— |
flip() + shift() |
avoidCollisions (standart true) |
data-side o'zgaradi |
shift({ padding }) |
collisionPadding |
— |
size() |
— | --radix-popover-content-available-height |
Shuning uchun Radix chuqur darsida "animatsiya yo'nalishini data-side ga qarab tanlang" degan edik: side="bottom" yozsangiz ham, flip uni top qilishi mumkin.
Tekshirib ko'ring: Tooltip tugma ostida chiqishi kerak (
side="bottom"), lekin tugma ekranning eng pastida. Ekranda nima ko'rasiz vadata-sidenima bo'ladi?
Javob
Tooltip tugma ustida chiqadi va data-side="top" bo'ladi. avoidCollisions (ya'ni flip) pastda joy yo'qligini ko'rib, qarama-qarshi tomonga ag'dardi. Agar strelka yoki animatsiya side prop'iga qarab yozilgan bo'lsa, u noto'g'ri tomonga qaraydi — shuning uchun stil [data-side=top] ga qarab yoziladi. Natija oynasida buni ko'rib bo'lmaydi (tekshiruvchida joylashuv hisoblanmaydi), haqiqiy brauzerda — oynani kichraytirib sinang.
8. URL bilan modal
Jasur aka buyurtmani tahrirlash oynasini ochib, havolani Sardorga yubormoqchi bo'ldi. Lekin URL o'zgarmagan — Sardor oddiy ro'yxatni ko'rdi. Yana bir muammo: telefonda «Orqaga» bosilganda oyna yopilmadi, butun sahifa orqaga ketdi.
Yechim — oynaning holatini URL'da saqlash (So'rov parametrlari darsidagi g'oya): ?tahrir=12 bo'lsa — oyna ochiq.
import * as Dialog from "@radix-ui/react-dialog";
import {
MemoryRouter, useLocation, useNavigate, useSearchParams,
} from "react-router";
function Orders() {
const [params, setParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
const id = params.get("tahrir");
console.log("URL:", location.pathname + location.search);
return (
<div>
<button onClick={() => setParams({ tahrir: "12" })}>
Tahrirlash: Osh
</button>
<Dialog.Root
open={id !== null}
onOpenChange={(open) => {
if (!open) navigate(-1); // ochilish yozuviga qaytamiz
}}
>
<Dialog.Portal>
<Dialog.Content aria-describedby={undefined}>
<Dialog.Title>Buyurtma №{id}</Dialog.Title>
<Dialog.Close>Yopish</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
);
}
export default function App() {
return (
<MemoryRouter initialEntries={["/buyurtmalar"]}>
<Orders />
</MemoryRouter>
);
}Konsolda:
URL: /buyurtmalar
URL: /buyurtmalar?tahrir=12
URL: /buyurtmalar- Ochish —
setParamsURL'ga?tahrir=12ni yangi tarix yozuvi sifatida qo'shadi (push). Oyna —open={id !== null}, ya'ni controlled, holat manbai URL. - Yopish (Esc, «Yopish», fon) —
navigate(-1): tarixda bir qadam orqaga. URL yana/buyurtmalar. - «Orqaga» tugmasi brauzerda ham xuddi shunday ishlaydi: URL o'zgaradi →
idnull→ oyna yopiladi. Telefondagi odatiy harakat endi to'g'ri. - Havola
/buyurtmalar?tahrir=12— ochilgan oyna bilan keladi.
Natija oynasida manzil satri yo'q, shuning uchun MemoryRouter (SPA routing darsidagi qoida); loyihada — createBrowserRouter. Chuqurroq naqsh — fon sahifani saqlab qoladigan "marshrut-modal" (React Router'da location.state bilan) — katta ilovalarda ishlatiladi.
Diqqat:
navigate(-1)bitta holatda xato: odam oynaga havola orqali to'g'ridan kirgan bo'lsa (yangi tab). Unda tarixda orqada hech narsa yo'q —navigate(-1)ilovadan chiqib ketadi yoki hech narsa qilmaydi. Buni 3-mashqda tuzatasiz.
9. Vazifalar qadami: Radix tooltip (holat 11 boshlanadi)
9.1 Nima va nega
Keyingi darsda vazifalar ga Ctrl+K bilan ochiladigan buyruqlar oynasi qo'shiladi. Uning tugmasi ustida tooltip yorliqni ko'rsatadi: «Tezkor ochish: Ctrl+K». Bugun shu tooltip'ning o'zini qo'shamiz. Commit — keyingi darsda, holat 11 bitta qadam.
Modal, popover va drawer uchun kanonda o'zgarish yo'q: tasdiq oynasi native <dialog> da qoladi (CSP — UI kutubxonalar xaritasi va Radix chuqur darslaridagi o'lchov), popover va vaul — faqat tajriba. Kanon raqamlari (kutubxonalar.txt, faqat React'ga nisbatan):
- Radix DialogCSP 113,6 KB
- Radix TooltipCSP 018,5 KB
- vaul (drawer)CSP 321,4 KB
- Radix PopoverCSP 024 KB
Manba: O'lchandi: tajriba/kitlar, Vite 8.3.3, React 19.3.0, Chrome 155, natijalar/tajriba/kutubxonalar.txt, 2026-10-14
Bu raqamlar "faqat React" ilovaga nisbatan. vazifalar da Radix menyu allaqachon bor — umumiy qismlar (Floating UI, Portal, Presence) qayta yuklanmaydi. Shuning uchun tooltip vazifalar ning birinchi yuklashiga atigi +4.1 KB qo'shdi (kanon tuzatish o'lchovi). Tooltip lazy qilinmadi: u birinchi ekranda kerak.
git switch -c feat/buyruqlar
npm i -E @radix-ui/react-tooltip@1.3.09.2 ui/tooltip.tsx
shadcn'dan ko'chirilgan (npx shadcn@4.21.4 add tooltip, shadcn/ui I dagi oqim) va moslangan. Kanon fayli (sarlavha izohi qisqartirilgan):
// tooltip.tsx — shadcn/ui'dan KO'CHIRILGAN (20/#10) va moslangan:
// alohida paket @radix-ui/react-tooltip, bizning tokenlar,
// portal — <main> da, o'q (Arrow) olib tashlandi
import type { ComponentProps } from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/ui/cn.ts";
import { portalJoyi } from "@/ui/portal-joyi.ts";
// Provider — kechikish hamma tooltip uchun bitta (Qobiq'da): bir
// tooltip ochiq bo'lsa, qo'shnisi kechikishsiz ochiladi
// (skipDelayDuration)
export function TooltipProvider({
delayDuration = 300,
...props
}: ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
delayDuration={delayDuration}
{...props}
/>
);
}
export function Tooltip(
props: ComponentProps<typeof TooltipPrimitive.Root>,
) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
}
export function TooltipTrigger(
props: ComponentProps<typeof TooltipPrimitive.Trigger>,
) {
return (
<TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
);
}
// Matn trigger'ga aria-describedby bilan bog'lanadi (Radix): ekran
// o'quvchi tugma nomidan keyin tooltip matnini ham o'qiydi
export function TooltipContent({
className,
sideOffset = 4,
...props
}: ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal container={portalJoyi()}>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 rounded-burchak bg-matn px-3 py-1.5 text-sm text-fon shadow-soya",
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
);
}Bugungi dars g'oyalari kodda:
portalJoyi()— tooltip<main>ichiga chiziladi,<body>ga emas: axe'ningregionqoidasi (Radix chuqur dagi menyu bilan bir xil sabab).- Arrow olib tashlandi —
flippaytida strelka yo'nalishinidata-sidega qarab almashtirish kerak bo'lardi; kichik tooltip'ga bu murakkablik shart emas. bg-matn text-fon— teskari ranglar: matn rangi fonga, fon matnga. Qorong'i temada o'zi almashadi (shadcn/ui I dagi tokenlar).TooltipContentdagi Tailwind qatori 76 belgi — kanon kodi o'zgarmaydi.
9.3 Provider — Qobiq.tsx da
+import { TooltipProvider } from "../ui/tooltip.tsx";
...
- <FoydalanuvchiMenyusi />
+ {/* 20/#10–#11: buyruqlar oynasi (Ctrl+K) va foydalanuvchi
+ menyusi bir qatorda. TooltipProvider — tooltip'lar uchun
+ umumiy kechikish */}
+ <TooltipProvider>
+ <div className="flex flex-wrap items-center justify-between gap-2">
+ {kirgan && <BuyruqlarOynasi />}
+ <FoydalanuvchiMenyusi />
+ </div>
+ </TooltipProvider>BuyruqlarOynasi — keyingi darsning komponenti. Uning tugmasida tooltip shunday ulanadi (kanon, qisqartirilgan):
<Tooltip>
<TooltipTrigger asChild>
<Tugma id="buyruqlar-tugma" aria-haspopup="dialog" onClick={och}>
<Search size={16} />
Buyruqlar
</Tugma>
</TooltipTrigger>
<TooltipContent>{`Tezkor ochish: ${YORLIQ}`}</TooltipContent>
</Tooltip>asChild — trigger bizning Tugma (Radix chuqur). Tugma matni «Buyruqlar» — nom tugmada, tooltip — faqat yorliq haqida qo'shimcha.
9.4 Tekshiruv (holat 11, kanon e2e-20)
Tooltip uchun ikki tekshiruv (natija-20-11.txt, Chrome 155):
✅ tooltip: klaviatura fokusida chiqdi, tugmaga bog'langan (aria-describedby): ["Tezkor ochish: Ctrl+K",true,"Control+K"]
✅ tooltip: Esc — yopildi, fokus joyida: [true,"buyruqlar-tugma"]Bu «Radix Tooltip: Bobur yo'li» bo'limidagi natijaning haqiqiy Chrome'dagi takrori: fokusda chiqdi, aria-describedby bilan bog'landi, Esc — fokus joyida. Massivdagi "Control+K" — tugmaning aria-keyshortcuts atributi (keyingi darsda). axe — 7 ko'rinishda 0 buzilish (11-axe.txt). Testlar, build va commit — keyingi darsda, Ctrl+K bilan birga.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| modal ochiq, orqa sahifa aylanadi | native <dialog> skroll'ni bloklamaydi |
html:has(dialog:modal) { overflow: hidden } |
style-src xatosi modal ochilganda |
Radix Dialog/vaul <style> |
native <dialog> + CSS |
| tooltip ichidagi havolaga yetib bo'lmaydi | tooltip fokus olmaydi | popover |
| telefonda muhim ma'lumot ko'rinmaydi | u faqat tooltip'da | matnni tugmaga |
| strelka noto'g'ri tomonga qaraydi | stil side prop'ga qarab |
[data-side=…] |
| «Orqaga» oynani yopmaydi | oyna holati faqat useState da |
holat URL'da |
| havola bilan kirganda «Yopish» ilovadan chiqaradi | navigate(-1) — tarix bo'sh |
3-mashqdagi tekshiruv |
| Radix Dialog'da izoh ogohlantirishi | Description yo'q |
aria-describedby={undefined} |
11. Mashqlar
Mashqlar kurs/mashqlar/20/10-qatlamli-ui/ da.
1-mashq (oson): qaysi qatlam?
Har vaziyatga bittasi: modal, drawer, popover yoki tooltip.
- «Kartani o'chirish» tasdig'i:
- Ikonka tugma ustida «Nusxalash»:
- «Narx oralig'i» tugmasi yonida ikki maydon va «Qo'llash»:
- Telefonda pastdan chiqadigan savat:
Yechim
- Tasdiq — javob kerak, ish to'xtaydi:
modal(aniqrog'ialertdialog). - «Nusxalash» — faqat matn, qo'shimcha:
tooltip(tugmadaaria-labelham bo'lsin). - Ichida maydonlar va tugma bor, sahifa to'xtamaydi:
popover. - Chetdan chiqadigan modal, telefonda:
drawer.
2-mashq (o'rta): «Qo'llash» popover'ni yopsin
«Narx» popover'i ichida ikki tugma: «35 000 gacha» va «Qo'llash». «35 000 gacha» filtrni tanlaydi, popover ochiq qoladi. «Qo'llash» — filtrni sahifaga yozadi va popover'ni yopadi. Pastda Filtr: 35 000 gacha.
Ishora: popover controlled — open va onOpenChange (Radix chuqur dagi uchlik).
Narx
Filtr: 35 000 gachaYechim
import { useState } from "react";
import * as Popover from "@radix-ui/react-popover";
export default function App() {
const [open, setOpen] = useState(false);
const [draft, setDraft] = useState("—");
const [filter, setFilter] = useState("—");
function apply() {
setFilter(draft);
setOpen(false);
}
return (
<div>
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger>Narx</Popover.Trigger>
<Popover.Portal>
<Popover.Content sideOffset={8} aria-label="Narx filtri">
<button onClick={() => setDraft("35 000 gacha")}>
35 000 gacha
</button>{" "}
<button onClick={apply}>Qo'llash</button>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
<p>Filtr: {filter}</p>
</div>
);
}Ekranda:
Narx
Filtr: 35 000 gachaPopover yopildi — «Ekranda» da uning tugmalari yo'q. Ikki holat bor: draft (popover ichidagi tanlov) va filter (qo'llangani). «Qo'llash» bosilmasa, sahifa o'zgarmaydi — formadagi "saqlash" mantiqi. setOpen(false) — popover controlled bo'lgani uchun kod bilan yopish mumkin; fokus Radix tomonidan «Narx» tugmasiga qaytariladi. aria-label — popover dialog'ining nomi.
3-mashq (qiyin): havola bilan kirilgan modal
«URL bilan modal» bo'limidagi kodni tuzating: ilovaga to'g'ridan /buyurtmalar?tahrir=12 havolasi bilan kirilsa, yopish navigate(-1) emas, ?tahrir siz manzilga almashtirish (replace) bo'lsin. Ilova ichidan ochilganda esa — avvalgidek navigate(-1).
Ishora: ilova ichida birinchi tarix yozuvining location.key qiymati — "default" (React Router darslaridagi chekka holat). setParams({}, { replace: true }) — joriy yozuvni almashtiradi.
Havola bilan kirib, Esc bosilgandan keyingi konsol:
URL: /buyurtmalar?tahrir=12
URL: /buyurtmalarYechim
import * as Dialog from "@radix-ui/react-dialog";
import {
MemoryRouter, useLocation, useNavigate, useSearchParams,
} from "react-router";
function Orders() {
const [params, setParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
const id = params.get("tahrir");
console.log("URL:", location.pathname + location.search);
function close() {
if (location.key === "default") {
// havola bilan kirilgan: orqada ilova yozuvi yo'q
setParams({}, { replace: true });
} else {
navigate(-1); // ilova ichida ochilgan
}
}
return (
<div>
<button onClick={() => setParams({ tahrir: "12" })}>
Tahrirlash: Osh
</button>
<Dialog.Root
open={id !== null}
onOpenChange={(open) => !open && close()}
>
<Dialog.Portal>
<Dialog.Content aria-describedby={undefined}>
<Dialog.Title>Buyurtma №{id}</Dialog.Title>
<Dialog.Close>Yopish</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
);
}
export default function App() {
return (
<MemoryRouter initialEntries={["/buyurtmalar?tahrir=12"]}>
<Orders />
</MemoryRouter>
);
}Konsolda:
URL: /buyurtmalar?tahrir=12
URL: /buyurtmalarOyna ochiq holda boshlandi (havola), Esc — location.key hali "default", shuning uchun replace: tarixda bitta yozuv qoladi, «Orqaga» endi ilovadan chiqadi — kutilgan xulq. Ilova ichida «Tahrirlash» bosilgan bo'lsa, yangi yozuvning kaliti tasodifiy satr bo'ladi va navigate(-1) ishlaydi. Radix fokusni Trigger ga qaytaradi — bu misolda trigger yo'q (oyna URL'dan ochilgan), shuning uchun haqiqiy ilovada fokusni o'zingiz tegishli joyga qo'yasiz (Fokus boshqaruvi).
4-mashq: Vazifalar qadami
feat/buyruqlar branch'ida: @radix-ui/react-tooltip@1.3.0 ni aniq versiya bilan o'rnating, src/ui/tooltip.tsx ni yarating («ui/tooltip.tsx» bo'limi) va Qobiq.tsx da TooltipProvider ni qo'shing. Commit hali qilinmaydi — keyingi darsda BuyruqlarOynasi bilan birga.
Yechim
Tekshiruv ro'yxati:
package.jsonda"@radix-ui/react-tooltip": "1.3.0"—^siz.npm run tip— 0 xato (hali hech kimTooltipni ishlatmaydi —knipishlatilmagan eksport haqida ogohlantirishi mumkin; keyingi darsda yo'qoladi).TooltipContent—portalJoyi()bilan<main>ga.- Qobiq'da
TooltipProvider— foydalanuvchi menyusi atrofida;BuyruqlarOynasiimporti keyingi darsda qo'shiladi.
12. Real ishda
- Dizayn tizimlari —
Dialog,Sheet(drawer),Popover,Tooltip— har birida alohida komponent va qoida: "tooltip ichida tugma yo'q", "tasdiq — AlertDialog". Kod ko'rigida turini noto'g'ri tanlash — ko'p uchraydigan izoh. - Mobil veb — «Orqaga» bilan yopiladigan oynalar (URL'da holat) foydalanuvchi uchun tabiiy; ko'p ilovalar buni unutadi.
- Intervyu savollari: "modal va popover farqi?", "fokus tuzog'i (focus trap) nima va qachon kerak?", "tooltip a11y talablari?", "Floating UI'da flip va shift farqi?", "modalni URL bilan qanday bog'laysiz?".
Xulosa
- To'rt qatlam: modal (to'xtatadi, javob kutadi), drawer (chetdan modal), popover (to'xtatmaydi, ichida boshqaruv), tooltip (faqat matn, fokus ko'chmaydi).
- Native
<dialog>— fokus, Esc, inert bepul; skroll bloklash — CSS (html:has(dialog:modal)), CSP'ga mos. Radix Dialog va vaul —<style>bilan. - AlertDialog:
alertdialogroli, fon bosilmaydi, fokus «Bekor qilish» da. - Tooltip: klaviaturada
instant-open,aria-describedby→role="tooltip", Esc'da fokus joyida; muhim matn — tugmada. - Floating UI:
offset→flip→shift→size; Radix'dasideOffset,avoidCollisions,data-side. - URL bilan modal: ochish — push, yopish —
navigate(-1)(havola bilan kirilganda —replace).
Keyingi dars: Command palette va klaviatura yorliqlari — Ctrl+K bilan vazifalar buyruqlar oynasi: cmdk, o'zbekcha apostrofni biladigan qidiruv, Mac'da K, maydon ichidagi yorliqlar va cmdk'ni faqat kerak bo'lganda yuklash.
Manbalar
- MDN:
<dialog>,HTMLDialogElement.showModal(), Popover API — developer.mozilla.org - Radix Primitives: Dialog, Alert Dialog, Popover, Tooltip — radix-ui.com/primitives (Tooltip 1.3.0)
- Floating UI: Middleware (offset, flip, shift, size), autoUpdate — floating-ui.com (
@floating-ui/react0.27.20) - vaul — github.com/emilkowalski/vaul (1.1.2)
- WCAG 2.2: 1.4.13 Content on Hover or Focus — w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus
- WAI-ARIA APG: Dialog (Modal), Alert Dialog, Tooltip patterns — w3.org/WAI/ARIA/apg/patterns
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!