IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya8/30-dars19 daqiqa
Mundarija (29)

React'da ikonkalar: lucide-react, a11y va bundle hajmi

Qisqacha: Har lucide ikonka — alohida React komponent: import { Pencil } from "lucide-react", keyin <Pencil size={16} />. Ikonka currentColor bilan matn rangini oladi va o'zi aria-hidden="true" qo'yadi — ya'ni bezak. Faqat ikonkali tugmaning nomi aria-label da bo'lishi shart. Nomli import faqat ishlatilgan ikonkalarni bundle'ga oladi (vazifalar da 9 ta ikonka — +2.0 KB), icons[nom] esa hammasini (+161 KB).

Bu darsda

  • lucide ikonkasi DOM'da qanday <svg> bo'lishini va props'lari (size, strokeWidth, absoluteStrokeWidth, color) nima o'zgartirishini jonli ko'rasiz.
  • Ikonka qachon bezak (aria-hidden) va qachon ma'noli ekanini, faqat ikonkali tugmaga nom berishni bilasiz.
  • Ikonkani matn bilan bir qatorga tekislaysiz (inline-flex, gap, shrink-0).
  • Nomli import, icons[nom] va DynamicIcon ning bundle'ga ta'sirini kanon o'lchovida ko'rasiz.
  • react-icons, Heroicons, Tabler va Iconify'ni farqlaysiz.
  • vazifalar tugmalariga ikonka qo'shasiz (holat 08) va e2e testi nega yiqilganini tushunasiz.

Oldin bilishingiz kerak: SVG HTML ichida va ikonkalar, SVG'ni CSS bilan stillash va ikonka tizimi, Rasm, SVG va assetlar komponentlarda, shadcn/ui I.

1. Nega bu kerak?

vazifalar dagi tugmalar hozircha faqat so'z: «Tahrirlash», «O'chirish», «Saqlash». Uch vazifada bu yetadi. Lekin ro'yxat uzaysa, ko'z so'zni o'qimaydi — shaklni qidiradi. Qalam belgisi — tahrir, chelak — o'chirish. Bu belgilar butun dunyoda bir xil tushuniladi.

Rasm, SVG va assetlar darsida SVG'ni qo'lda komponentga aylantirgan edik. 40 ta ikonka kerak bo'lsa-chi? Har birini Figma'dan olib, tozalab, komponent qilish — bir kun. Tayyor ikonka kutubxonasi buni bitta importga aylantiradi.

Lekin ikkita xavf bor. Birinchisi — a11y: ikonka ko'z uchun, Malika (ekran o'quvchi) uchun esa u yo jim, yo "rasm" deb noma'lum. Ikkinchisi — hajm: noto'g'ri import bilan bitta ikonka uchun mingtasini yuklab yuborish mumkin. Bugun ikkalasini ham o'lchab ko'ramiz.

Hayotiy o'xshatish: yo'l belgilari. Haydovchi matnni o'qimaydi — shaklni taniydi. Lekin belgining ostida doim kichik yozuv ham bor: shakl tushunarsiz bo'lsa, so'z yordam beradi.

2. lucide-react: har ikonka — komponent

2.1 Birinchi ikonka

lucide-react — Lucide to'plamining React varianti. SVG ikonka tizimi darsida Lucide'ni tilga olgan edik. Kanon versiyasi — 1.52.0:

bash
npm i -E lucide-react@1.52.0

Natija oynasida lucide bor (tanlangan ~109 ikonka). Ikonkani chizamiz va DOM'dagi atributlarini o'qiymiz:

jsx
import { useEffect } from "react";
import { Pencil } from "lucide-react";

export default function App() {
  useEffect(() => {
    const svg = document.querySelector("svg");
    for (const a of svg.attributes) {
      console.log(a.name, "=", a.value);
    }
  }, []);
  return (
    <button>
      <Pencil /> Tahrirlash
    </button>
  );
}

Konsolda:

text
xmlns = http://www.w3.org/2000/svg
width = 24
height = 24
viewBox = 0 0 24 24
fill = none
stroke = currentColor
stroke-width = 2
stroke-linecap = round
stroke-linejoin = round
class = lucide lucide-pencil
aria-hidden = true

Ekranda:

text
Tahrirlash

Qatorlarni o'qiymiz:

  • viewBox = 0 0 24 24 — ikonka 24 × 24 katakli "chizma qog'ozi"da chizilgan. width/height qancha bo'lmasin, rasm shu nisbatda cho'ziladi (SVG HTML ichida).
  • fill = none, stroke = currentColor — Lucide — chiziqli (outline) ikonkalar: ichi bo'sh, faqat chiziq. Chiziq rangi — currentColor, ya'ni ota elementning matn rangi. Tugma matni qizil bo'lsa, ikonka ham qizil — alohida rang berish shart emas.
  • class = lucide lucide-pencil — CSS'da topish uchun: barcha lucide ikonkalari va aynan qalam.
  • aria-hidden = true — ikonka ekran o'quvchidan yashirilgan. Malika «Tahrirlash, tugma» ni eshitadi, "rasm" ni emas.

"Ekranda" faqat «Tahrirlash» — SVG ichida matn yo'q.

2.2 Props: o'lcham, chiziq, rang

Bitta ikonka besh xil:

jsx
import { useEffect } from "react";
import { Trash2 } from "lucide-react";

export default function App() {
  useEffect(() => {
    for (const s of document.querySelectorAll("svg")) {
      console.log(
        s.getAttribute("width"),
        s.getAttribute("stroke-width"),
        s.getAttribute("stroke"),
      );
    }
  }, []);
  return (
    <div style={{ fontSize: 40 }}>
      <Trash2 />
      <Trash2 size={16} />
      <Trash2 size={48} strokeWidth={1.5} color="#c0392b" />
      <Trash2 size={48} absoluteStrokeWidth />
      <Trash2 size="1em" />
    </div>
  );
}

Konsolda:

text
24 2 currentColor
16 2 currentColor
48 1.5 #c0392b
48 1 currentColor
1em 2 currentColor
  • size — eni va bo'yi birga. Standart — 24.
  • strokeWidth — chiziq qalinligi viewBox birliklarida. 48 pikselli ikonkada 1.5 birlik ekranda 3 piksel bo'ladi.
  • absoluteStrokeWidth — chiziq ekranda doim 2 piksel bo'lsin. 48 pikselda lucide stroke-width ni o'zi 1 ga tushirdi: 2 × 24 / 48 = 1. Katta va kichik ikonkalar yonma-yon tursa, chiziqlar bir xil qalin ko'rinadi.
  • color — stroke ga yoziladi. Odatda kerak emas: currentColor yaxshiroq, rang CSS'dan keladi.
  • size="1em" — ikonka shrift o'lchamiga ergashadi (bu yerda 40px). Sarlavha ichida katta, izohda kichik — avtomatik.

Maslahat: Ikonka nomlari: Trash2 — 1.x da trash ning taxallusi (alias). Shuning uchun sinfida lucide-trash ham bor. Eski kodda Trash2, yangisida Trash — ikkalasi bitta ikonka. Natija oynasida ro'yxatda yo'q ikonkani import qilsangiz (masalan Rocket), o'zbekcha xato barcha mavjud nomlar ro'yxati bilan chiqadi — loyihangizda esa 1 866 ta ikonkaning hammasi bor (1.52.0 versiyasining icons obyektida sanadik).

3. A11y: bezak yoki ma'no

3.1 Uch holat

Ikonka sahifada uch xil vazifada keladi:

Holat Misol Kim nom beradi
Matn yonida — bezak « Tahrirlash» matn; ikonka aria-hidden
Yolg'iz, tugma ichida «» tugmasi tugmaning aria-label i
Yolg'iz, ma'lumot sifatida jadvalda " to'langan" belgisi ikonkaning o'zi (role="img" + aria-label)

Birinchisi — eng ko'p. Lucide uni standart qilib qo'ygan, biz hech narsa yozmaymiz.

3.2 Faqat ikonkali tugma

Telefonda joy tor: qatordagi «O'chirish» faqat chelak bo'lib qoldi. Sardor yozdi:

jsx
import { useEffect } from "react";
import { Trash2 } from "lucide-react";

export default function App() {
  useEffect(() => {
    const [bad, good] = document.querySelectorAll("button");
    console.log("❌ matn:", JSON.stringify(bad.textContent));
    console.log("❌ aria-label:", bad.getAttribute("aria-label"));
    console.log("✅ aria-label:", good.getAttribute("aria-label"));
    console.log(
      "✅ ikonka aria-hidden:",
      good.querySelector("svg").getAttribute("aria-hidden"),
    );
  }, []);
  return (
    <div>
      <button>
        <Trash2 size={16} />
      </button>{" "}
      <button aria-label="O'chirish: Non olish">
        <Trash2 size={16} />
      </button>
    </div>
  );
}

Konsolda:

text
❌ matn: ""
❌ aria-label: null
✅ aria-label: O'chirish: Non olish
✅ ikonka aria-hidden: true

Birinchi tugmaning nomi yo'q: matn bo'sh, aria-label yo'q, ikonka esa yashirin. Malika faqat «tugma» ni eshitadi — nima qilishini bilmaydi. axe buni button-name qoidasi bilan ushlaydi (React'da accessibility). Ikkinchisida nom tugmada, ikonka bezak bo'lib qoldi — to'g'ri yo'l. vazifalar dagi Tugma 18-qismdan beri buni tip darajasida majburlaydi: matnsiz va aria-label siz <Tugma /> — tsc xatosi.

Ko'zi ko'radigan foydalanuvchi uchun ham nom foydali: faqat ikonkali tugmaga tooltip qo'shiladi — lekin u aria-label o'rnini bosmaydi.

3.3 Ikonkaga aria-label bersa-chi?

jsx
import { useEffect } from "react";
import { CircleCheck } from "lucide-react";

export default function App() {
  useEffect(() => {
    const svg = document.querySelector("svg");
    console.log("aria-hidden:", svg.getAttribute("aria-hidden"));
    console.log("aria-label:", svg.getAttribute("aria-label"));
    console.log("role:", svg.getAttribute("role"));
  }, []);
  return (
    <p>
      Buyurtma №12 <CircleCheck size={16} aria-label="to'langan" />
    </p>
  );
}

Konsolda:

text
aria-hidden: null
aria-label: to'langan
role: null

Lucide aria-label ni ko'rib, aria-hidden ni qo'ymadi — ikonka ekran o'quvchiga ochildi. Lekin role yo'q. <svg> ga rol berilmasa, ba'zi ekran o'quvchi va brauzer juftliklari uni rasm deb tanimaydi va nomini o'qimaydi. Ishonchli shakl — role="img" ni o'zingiz qo'shish: <CircleCheck role="img" aria-label="to'langan" />. Yanada ishonchlisi — ko'rinmas matn: <span className="sr-only">to'langan</span> (React'da accessibility dagi "faqat ekran o'quvchi uchun" sinfi).

Tekshirib ko'ring: «Tahrirlash» tugmasida qalam ikonkasi va matn bor. Sardor ikonkaga ham aria-label="qalam" qo'shdi. Malika nimani eshitadi?

Javob

Taxminan «qalam Tahrirlash, tugma». aria-label qo'shilgani uchun lucide aria-hidden ni olib tashladi va ikonka tugma nomining bir qismi bo'ldi. Ma'lumot takrorlandi va chalkashdi: "qalam" — rasmning tavsifi, harakat emas. Qoida: matn yonidagi ikonka — doim bezak, unga nom bermang.

4. Matn bilan bir qatorda

Ikonka va matnni yonma-yon qo'ysangiz, ko'pincha ikonka biroz yuqorida yoki pastda "suzadi". Sabab: <svg> — inline element, u matnning tayanch chizig'iga (baseline) qo'yiladi. Ikki yo'l bor.

1. Tugma — inline-flex. Kanon Tugma ning asos sinflari (holat 08):

text
tugma inline-flex min-h-bosish items-center justify-center gap-2 …
[&_svg]:shrink-0
  • inline-flex items-center — ikonka va matn vertikal markazda (Flexbox).
  • gap-2 — orasida 8 piksel; matn oldiga bo'sh joy ({" "}) shart emas.
  • [&_svg]:shrink-0 — tugma tor bo'lib qolsa, flex elementlari qisiladi. Ikonka qisilsa — buzilgan chizma. shrink-0 uni saqlaydi, uzun matn esa qatorga o'raladi (flex-shrink). [&_svg] — Tailwind'ning ixtiyoriy variant'i: "ichidagi svg ga" (Tailwind variantlar).

2. Matn ichida — vertical-align. Havola matni ichida ikonka (kanon «Batafsil ›»):

tsx
Batafsil
<ChevronRight size={16} className="inline align-[-0.15em]" />

align-[-0.15em] — vertical-align: -0.15em: ikonkani tayanch chizig'idan biroz pastga tushirish. em — shrift o'lchamiga nisbatan, shuning uchun har o'lchamda to'g'ri.

5. Bundle hajmi: nomli import va icons[nom]

5.1 Tree-shaking

Tree-shaking (daraxtni silkitish) — bundler ishlatilmagan eksportlarni tashlab yuborishi (Tree shaking). lucide-react'ning package.json ida "sideEffects": false — "importning yon ta'siri yo'q, ishlatilmaganini bemalol tashlang". Har ikonka alohida modul. Shuning uchun:

tsx
import { Check, Pencil, Trash2, X } from "lucide-react";

— build'ga faqat shu to'rttasi tushadi, 1 866 tasining qolgani emas.

5.2 Kanon o'lchovi

Kanon vazifalar da (holat 08, 9 ta ikonka) uch variantni o'lchadi (natijalar/08-ikonka-hajm.txt, birinchi yuklash JS, gzip):

text
lucide-react 1.52.0 — nomli import (kanon)   568126  181102  156126
qo'lda inline SVG (o'sha 9 yo'l)             564850  179748  155077
lucide icons[nom] — nom satrdan              1191965  340254  278994
# holat 04 (ikonkasiz): 563418 179115 154705

Ustunlar: xom bayt, gzip, brotli. Holat 04 dan farqi:

9 ta ikonka: birinchi yuklashga qo'shimcha (gzip)
  • qo'lda inline SVG0,6 KB
  • lucide — nomli importkanon2 KB
  • lucide — icons[nom]161,1 KB

Manba: O'lchandi: holat 08, Vite 8.3.3, gzip -9, natijalar/08-ikonka-hajm.txt, 2026-10-14

Uch xulosa:

  1. Nomli import — +2.0 KB. Qo'lda yozilgan SVG'dan atigi 1.4 KB ko'p. Shu 1.4 KB evaziga: bir xil uslub, size/strokeWidth props, avtomatik aria-hidden va yangilanishlar.
  2. icons[nom] — +161 KB. Bu vazifalar ning butun 19-qismgacha bo'lgan kodidan ham ko'p!
  3. Bitta qator farq — 80 baravar narx.

5.3 icons[nom] tuzog'i

Bu kod qayerdan keladi? Ikonka nomi serverdan yoki sozlamadan satr bo'lib kelsa (masalan, taom turiga qarab ikonka):

tsx
// ❌ butun to'plam bundle'ga
import { icons } from "lucide-react";

function TaomIkonkasi({ nom }: { nom: string }) {
  const Ikonka = icons[nom as keyof typeof icons];
  return <Ikonka size={16} />;
}

icons — 1 866 ta ikonka yig'ilgan obyekt. Bundler qaysi kalit kerakligini build paytida bilolmaydi: nom faqat ishlash paytida ma'lum. Shuning uchun hammasini oladi.

Ikonkalar to'plami ma'lum bo'lsa — aniq xarita. Kanon AmalTugmasi.tsx aynan shunday:

tsx
import { Check, Pencil, Trash2, X } from "lucide-react";
import type { LucideIcon } from "lucide-react";

// Record<Amal, ...> emas: "almashtir" — katak (checkbox), tugma emas
const IKONKALAR: Partial<Record<Amal, LucideIcon>> = {
  tahrirla: Pencil,
  ochir: Trash2,
  saqla: Check,
  bekor: X,
};

Xaritada to'rtta nom — build to'rttasini oladi. Partial — har amalga ikonka shart emas. LucideIcon — ikonka komponentining turi.

Ikonkalar oldindan ma'lum bo'lmasa (masalan, foydalanuvchi o'z kategoriyasiga ikonka tanlaydi) — lucide-react/dynamic dagi DynamicIcon: <DynamicIcon name="pizza" />. U har ikonkani alohida kichik chunk sifatida, kerak bo'lganda yuklaydi (lazy va code splitting g'oyasi). Birinchi yuklash shishmaydi, lekin ikonka tarmoqdan keladi — bir lahza bo'sh joy.

Jonli misolda xarita naqshi:

jsx
import { useEffect } from "react";
import { Check, Pencil, Trash2, X } from "lucide-react";

const ICONS = {
  edit: Pencil,
  delete: Trash2,
  save: Check,
  cancel: X,
};

function ActionButton({ action, children }) {
  const Icon = ICONS[action];
  return (
    <button>
      {Icon !== undefined && <Icon size={16} />}
      {children}
    </button>
  );
}

export default function App() {
  useEffect(() => {
    for (const b of document.querySelectorAll("button")) {
      const svg = b.querySelector("svg");
      const icon = svg ? svg.classList[1] : "ikonkasiz";
      console.log(b.textContent, "→", icon);
    }
  }, []);
  return (
    <div>
      <ActionButton action="edit">Tahrirlash</ActionButton>{" "}
      <ActionButton action="delete">O'chirish</ActionButton>{" "}
      <ActionButton action="toggle">Bajarildi</ActionButton>
    </div>
  );
}

Konsolda:

text
Tahrirlash → lucide-pencil
O'chirish → lucide-trash
Bajarildi → ikonkasiz

Xaritada yo'q amal (toggle) — undefined, tugma ikonkasiz chiziladi, xato yo'q. Icon katta harf bilan — JSX'da kichik harfli nom HTML teg deb o'qiladi (Komponentlar).

Tekshirib ko'ring: Jamoadoshingiz import * as Icons from "lucide-react" yozdi va <Icons.Pencil /> ishlatdi. Bundle +161 KB bo'ladimi?

Javob

Odatda yo'q. Icons.Pencil — xususiyatga statik murojaat: bundler build paytida faqat Pencil ishlatilganini ko'radi va qolganini tashlaydi (Vite/Rolldown namespace importini shunday tahlil qiladi). Muammo Icons[nom] — kvadrat qavsda o'zgaruvchi — bo'lganda boshlanadi: unda kalit faqat ishlash paytida ma'lum. Ishonch kerak bo'lsa — build'dan keyin hajmni o'lchang, kanon qilganidek.

6. Boshqa ikonka kutubxonalari

Kanon faqat lucide'ni o'lchadi, quyidagilar uchun hajm raqami aytmaymiz. Paketlarni o'rnatib, tuzilmasini ko'rdik (2026-10-17):

Kutubxona Tuzilma Nimaga e'tibor
react-icons 5.7.0 ko'p to'plam bitta paketda: react-icons/fa, /md, /lu (Lucide), /tb (Tabler)… to'plamni subpath'dan import qiling
@heroicons/react 2.2.0 Tailwind jamoasidan; /24/outline, /24/solid, /20/solid, /16/solid o'lchamga qarab alohida chizilgan
@tabler/icons-react 3.49.0 6 308 eksport, nomlar IconPencil kabi chiziq qalinligi — stroke prop
@iconify/react 6.0.2 ko'plab to'plamlar "nomi" bilan: <Icon icon="mdi:home" /> ikonkani ishlash paytida api.iconify.design dan yuklaydi

Iconify'ga alohida e'tibor: paket kodida api.iconify.design manzili bor — ikonka ma'lumoti tarmoqdan keladi. vazifalar ning CSP'sida connect-src tashqi manzilni to'sadi: ikonka chiqmaydi. Kanon tajribasida dotLottie va Rive ham xuddi shunday CDN'dan yuklashga uringan va to'silgan (kutubxonalar.txt). Iconify'da ikonkalarni oldindan bundle'ga qo'shish yo'li bor (oflayn rejim) — CSP'li loyihada aynan shu kerak.

Tanlov qoidasi oddiy: shadcn loyihalari standart bo'yicha lucide ishlatadi (components.json dagi "iconLibrary": "lucide", shadcn/ui I darsida). Bitta loyihada bitta ikonka to'plami — aks holda chiziq qalinligi va uslub aralashadi.

7. Vazifalar qadami: tugmalarda lucide ikonkalari (holat 08)

7.1 Nima o'zgaradi

Qatordagi amallar, «Qo'shish», hisobot va foydalanuvchi menyusi — 9 ta ikonka: Pencil, Trash2, Check, X, Plus, ChevronRight, Download, ChevronDown, LogOut. Hammasi — matn yonida, ya'ni bezak.

bash
git switch -c feat/ikonkalar
npm i -E lucide-react@1.52.0

7.2 Fayllar

ui/Tugma.tsx — asos sinflariga inline-flex … gap-2 … [&_svg]:shrink-0 («Matn bilan bir qatorda» bo'limi). Kanon qatori 120 belgi — kanon kodi o'zgarmaydi:

diff
 const tugmaSinflari = cva(
-  "tugma min-h-bosish rounded-burchak border px-3 py-2 disabled:opacity-60",
+  // 20/#08: inline-flex + gap — ikonka (svg) matn bilan bir qatorda
+  "tugma inline-flex min-h-bosish items-center justify-center gap-2 rounded-burchak border px-3 py-2 disabled:opacity-60 [&_svg]:shrink-0",

komponentlar/AmalTugmasi.tsx — IKONKALAR xaritasi («icons[nom] tuzog'i» bo'limida to'liq) va chizish:

diff
+import { Check, Pencil, Trash2, X } from "lucide-react";
+import type { LucideIcon } from "lucide-react";
 ...
 export function AmalTugmasi({ ref, amal, matn, nomi, onClick }: Props) {
+  const Ikonka = IKONKALAR[amal];
   return (
     <Tugma
       ref={ref}
       data-amal={amal}
       aria-label={`${matn}: ${nomi}`}
       onClick={onClick}
     >
+      {Ikonka !== undefined && <Ikonka size={16} />}
       {matn}
     </Tugma>

Kanon izohi tanlovni o'zi tushuntiradi (qisqartirib): "har ikonka alohida komponent, nomi bilan import qilinadi: build faqat shu to'rttasini oladi. Ikonka — bezak: matn ko'rinib turadi, lucide o'zi aria-hidden="true" qo'yadi".

Qolgan joylar — bittadan qator:

diff
 // YangiVazifaForma.tsx
     <Tugma type="submit" id="qoshish-tugma" variant="asosiy">
+      <Plus size={16} />
       {pending ? "Qo'shilmoqda…" : "Qo'shish"}

 // OddiyQator.tsx — havola matnidan keyin
         Batafsil
+        <ChevronRight size={16} className="inline align-[-0.15em]" />

 // BatafsilStatistika.tsx
           <Tugma className="hisobot-tugma data-[state=open]:border-asosiy">
+            <Download size={16} />
             Hisobotni yuklab olish

 // FoydalanuvchiMenyusi.tsx
             {foydalanuvchi.ism}
+            <ChevronDown size={16} />
 ...
           <DropdownMenuItem onSelect={chiqish}>
+            <LogOut size={16} />
             Chiqish

ChevronDown — menyu ochilishini bildiradi. Ekran o'quvchiga buni aria-haspopup="menu" allaqachon aytadi (Radix chuqur), shuning uchun ikonka bezak bo'lib qolaveradi.

7.3 e2e testi yiqildi — va bu yaxshi

Brauzer ssenariysining birinchi ishga tushishi: 159 dan 158 o'tdi. Yiqilgani — 13-qismdagi XSS tekshiruvi (XSS va DOM xavfsizligi). U hujum matnini ("><svg onload=alert(2)>) qo'shib, keyin sahifadagi barcha img va svg ni sanardi: 0 bo'lishi kerak. Endi tugmalarda bizning svg'larimiz bor!

Tuzatish (kanon e2e.mjs, o'zgarish 9):

js
// 20 (o'zgarish 9): ilovaning o'z ikonkalari
// (lucide-react, class="lucide")
tekshir("XSS: hech qanday alert/onerror bajarilmadi",
  [ogohlar, await p1.$$eval("img, svg:not(.lucide)",
    (l) => l.length)], [[], 0]);

svg:not(.lucide) — "lucide sinfi bo'lmagan svg" (Mantiqiy pseudo-klasslar). Hujum matnidan yasalgan svg'da bu sinf yo'q — u baribir sanaladi. Tekshiruvning ma'nosi o'zgarmadi, faqat o'z ikonkalarimiz chiqarildi. Shundan keyin: 159 .

Saboq: test yiqilganda testni o'chirmaymiz — nega yiqilganini tushunamiz. Bu yerda ilova to'g'ri, test esa yangi haqiqatni bilmas edi.

7.4 Tekshiruvlar va commit

bash
git add -A
git commit

Commit (kanon): feat: tugmalarda lucide ikonkalari (bezak, aria-hidden). Diff: 8 fayl, +45 −1.

Haqiqiy natijalar (holat 08, tekshiruv-08.txt):

  • npm test — node:test pass 198, Vitest Tests 65 passed (65); lint, format:check, tip, knip — exit 0.
  • Birinchi yuklash JS gzip: 179.1 → 181.1 KB (+2.0, «Kanon o'lchovi» bo'limidagi qator).
  • Brauzer: e2e 159 (o'zgarish 9 bilan), e2e-18 72, e2e-19 100, e2e-20 17 — hammasi . axe — 0 buzilish (08-axe.txt).

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
axe button-name, Malika "tugma" deydi faqat ikonkali tugmada nom yo'q tugmaga aria-label
"qalam Tahrirlash" eshitiladi matn yonidagi ikonkaga aria-label bezak ikonkaga nom bermang
bundle +161 KB icons[nom] aniq xarita yoki DynamicIcon
ikonka matndan yuqorida/pastda inline svg tayanch chizig'ida inline-flex items-center yoki vertical-align
tor tugmada ikonka qisilgan flex shrink shrink-0
Iconify ikonkasi chiqmaydi, CSP xatosi api.iconify.design ga connect-src oflayn to'plam
testda svg soni o'zgardi o'z ikonkalaringiz svg:not(.lucide)

9. Mashqlar

Mashqlar kurs/mashqlar/20/08-ikonkalar/ da.

1-mashq (oson): props

To'ldiring:

  • Ikonka shrift o'lchamiga ergashsin: size=
  • 32 pikselli ikonkada chiziq ekranda 2 piksel qolsin: prop
  • Lucide chiziq rangi standart bo'yicha: stroke=
  • Matn yonidagi ikonkaga lucide qo'yadigan atribut:
Yechim
  • size="1em" — width/height 1em bo'ladi, font-size ga ergashadi.
  • absoluteStrokeWidth — lucide stroke-width ni o'lcham bo'yicha qayta hisoblaydi (32 pikselda 1.5).
  • currentColor — ota elementning color i.
  • aria-hidden="true" — ekran o'quvchidan yashiradi; aria-label bersangiz, qo'yilmaydi.

2-mashq (o'rta): telefondagi ikonka-tugmalar

«Bahor» buyurtmalar ro'yxatida har qatorda ikki kichik tugma: Pencil va Trash2, matnsiz. Ularga to'g'ri nom bering — taom nomi bilan. Konsol tugmalarning aria-label ini va ikonkaning aria-hidden ini chiqarsin.

jsx
import { useEffect } from "react";
import { Pencil, Trash2 } from "lucide-react";

const orders = ["Osh", "Manti"];

export default function App() {
  useEffect(() => {
    for (const b of document.querySelectorAll("button")) {
      const svg = b.querySelector("svg");
      const hidden = svg.getAttribute("aria-hidden");
      console.log(b.getAttribute("aria-label"), "| svg:", hidden);
    }
  }, []);
  return (
    <ul>
      {orders.map((o) => (
        <li key={o}>
          {o}{" "}
          <button>
            <Pencil size={16} />
          </button>{" "}
          <button>
            <Trash2 size={16} />
          </button>
        </li>
      ))}
    </ul>
  );
}

Kutilgan konsol:

text
Tahrirlash: Osh | svg: true
O'chirish: Osh | svg: true
Tahrirlash: Manti | svg: true
O'chirish: Manti | svg: true

Ishora: «Faqat ikonkali tugma» bo'limi; kanon AmalTugmasi dagi `${matn}: ${nomi}` naqshi.

Yechim
jsx
import { useEffect } from "react";
import { Pencil, Trash2 } from "lucide-react";

const orders = ["Osh", "Manti"];

export default function App() {
  useEffect(() => {
    for (const b of document.querySelectorAll("button")) {
      const svg = b.querySelector("svg");
      const hidden = svg.getAttribute("aria-hidden");
      console.log(b.getAttribute("aria-label"), "| svg:", hidden);
    }
  }, []);
  return (
    <ul>
      {orders.map((o) => (
        <li key={o}>
          {o}{" "}
          <button aria-label={`Tahrirlash: ${o}`}>
            <Pencil size={16} />
          </button>{" "}
          <button aria-label={`O'chirish: ${o}`}>
            <Trash2 size={16} />
          </button>
        </li>
      ))}
    </ul>
  );
}

Konsolda:

text
Tahrirlash: Osh | svg: true
O'chirish: Osh | svg: true
Tahrirlash: Manti | svg: true
O'chirish: Manti | svg: true

Nomga taom ham kirdi: Malika Tab bilan yurganda "Tahrirlash: Osh", "O'chirish: Osh" ni eshitadi — o'nta «O'chirish» dan qaysi biri ekanini biladi. Ikonkalar bezak bo'lib qoldi: nom tugmada, ikki marta o'qilmaydi.

3-mashq (qiyin): holat belgisi — ma'noli ikonka

Buyurtma holati jadvalda faqat ikonka bilan ko'rsatiladi: "paid" → CircleCheck («to'langan»), "waiting" → Clock («kutilmoqda»), noma'lum holat → HelpCircle («noma'lum holat»). Ikonka yolg'iz, ya'ni ma'noli: role="img" va aria-label bilan. StatusIcon komponentini yozing — xarita va zaxira bilan.

Ishora: «icons[nom] tuzog'i» bo'limidagi xarita; zaxira — ?? (??, ?. va mantiqiy tayinlash).

text
Osh: role=img, label=to'langan, hidden=null
Manti: role=img, label=kutilmoqda, hidden=null
Lag'mon: role=img, label=noma'lum holat, hidden=null
Yechim
jsx
import { useEffect } from "react";
import { CircleCheck, Clock, HelpCircle } from "lucide-react";

const STATUS = {
  paid: { Icon: CircleCheck, label: "to'langan" },
  waiting: { Icon: Clock, label: "kutilmoqda" },
};
const UNKNOWN = { Icon: HelpCircle, label: "noma'lum holat" };

function StatusIcon({ status }) {
  const { Icon, label } = STATUS[status] ?? UNKNOWN;
  return <Icon size={16} role="img" aria-label={label} />;
}

const orders = [
  { name: "Osh", status: "paid" },
  { name: "Manti", status: "waiting" },
  { name: "Lag'mon", status: "returned" },
];

export default function App() {
  useEffect(() => {
    for (const li of document.querySelectorAll("li")) {
      const svg = li.querySelector("svg");
      console.log(
        `${li.textContent.trim()}: role=${svg.getAttribute("role")},`,
        `label=${svg.getAttribute("aria-label")},`,
        `hidden=${svg.getAttribute("aria-hidden")}`,
      );
    }
  }, []);
  return (
    <ul>
      {orders.map((o) => (
        <li key={o.name}>
          {o.name} <StatusIcon status={o.status} />
        </li>
      ))}
    </ul>
  );
}

Konsolda:

text
Osh: role=img, label=to'langan, hidden=null
Manti: role=img, label=kutilmoqda, hidden=null
Lag'mon: role=img, label=noma'lum holat, hidden=null

STATUS[status] ?? UNKNOWN — xaritada yo'q holat (returned) zaxiraga tushadi, ilova yiqilmaydi. role="img" qo'shildi, aria-hidden yo'q — ikonka ekran o'quvchiga "rasm, to'langan" bo'lib o'qiladi. Rang (yashil/sariq) qo'shsangiz ham, ma'no faqat rangda qolmasin — shakl va nom bor (WCAG: rang yagona belgi emas).

4-mashq: Vazifalar qadami

feat/ikonkalar branch'ida «Vazifalar qadami» bo'limidagi oltita faylni o'zgartiring, lucide-react@1.52.0 ni aniq versiya bilan o'rnating va e2e XSS tekshiruvini svg:not(.lucide) ga moslang. Keyin commit.

Yechim

Tekshiruv ro'yxati:

  • DevTools Elements: har ikonka — <svg class="lucide lucide-…" aria-hidden="true">, tugma matni va aria-label joyida.
  • Tor oynada (360 px) ikonkalar qisilmagan, matn bilan markazda.
  • npm test — node 198, Vitest 65; npm run tip, lint, knip — toza.
  • Build: birinchi yuklash JS gzip taxminan +2 KB. Agar +160 KB atrofida bo'lsa — kodda icons importi qolgan.
  • e2e: XSS tekshiruvi svg:not(.lucide) bilan o'tadi; boshqa tekshiruvlar o'zgarmagan.

10. Real ishda

  • Dizayn tizimlari — ikonka to'plami tizimning bir qismi: bitta kutubxona, bitta chiziq qalinligi, o'lchamlar shkalasi (16, 20, 24). Kod ko'rigida "nega boshqa to'plamdan ikonka?" — tez-tez beriladigan savol.
  • Bundle tahlili — "nega bundle 160 KB o'sdi?" savolining ko'p uchraydigan javobi — icons yoki butun to'plam importi. Bundle tahlili vositalari buni darhol ko'rsatadi.
  • Intervyu savollari: "faqat ikonkali tugmani qanday accessible qilasiz?", "tree-shaking nima va qachon ishlamaydi?", "aria-hidden qachon kerak?".

Xulosa

  • lucide-react: har ikonka — komponent (<Pencil size={16} />), currentColor bilan matn rangini oladi.
  • size, strokeWidth, absoluteStrokeWidth, size="1em" — o'lcham va chiziq; rang — CSS'dan.
  • Matn yonidagi ikonka — bezak (aria-hidden, lucide o'zi qo'yadi); faqat ikonkali tugma — tugmaga aria-label; yolg'iz ma'noli ikonka — role="img" + aria-label.
  • Tekislash: inline-flex items-center gap-2 va shrink-0, matn ichida — vertical-align.
  • Nomli import +2.0 KB, icons[nom] +161 KB: nomlar aniq xaritada yoki DynamicIcon bilan.
  • Iconify ikonkalarni tarmoqdan oladi — CSP'li loyihada oflayn to'plam kerak.

Keyingi dars: Toast bildirishnomalar — sonner bilan toast.promise va action, aria-live qanday ishlaydi, toast qachon noto'g'ri tanlov, va nega vazifalar o'z toast'ida qoldi (CSP o'lchovi bilan).

Manbalar

  • Lucide: React guide, Accessibility, absoluteStrokeWidth, Dynamic icons — lucide.dev/guide/packages/lucide-react (1.52.0)
  • react-icons — react-icons.github.io; Heroicons — heroicons.com; Tabler Icons — tabler.io/icons; Iconify — iconify.design/docs
  • W3C: Using ARIA role="img"; WAI-ARIA APG: accessible names for buttons — w3.org/WAI/ARIA/apg
  • MDN: vertical-align, SVG stroke-width — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React'da ikonkalar: lucide-react, a11y va bundle hajmi — IlmHamroh