IlmHamroh
JavaScript Full-stack/18-qism. React chuqur33/42-dars19 daqiqa
Mundarija (30)

Dizayn tizimi asoslari React'da: tokenlar va variantlar

Qisqacha: Dizayn tizimi — ranglar, oraliqlar, burchaklar va komponent ko'rinishlari haqidagi qarorlar to'plami. Qarorlar tokenlarga aylanadi: --joy-2: 0.5rem, --rang-asosiy: light-dark(#1f6f4a, #7fd1a5). CSS qoidalari qiymat emas, token ishlatadi — dizayner bitta qiymatni o'zgartirsa, butun ilova o'zgaradi, qorong'i rejim esa faqat tokenlarni almashtiradi. Komponent variantlari (variant, size) tizimli xaritalanadi: oddiy CSS'da data-variant atributi bilan, Tailwind loyihalarida cva yoki tailwind-variants funksiyasi va cn() bilan. Figma'dagi o'zgaruvchilar W3C DTCG formatida eksport qilinib, kodga avtomatik o'tadi.

Bu darsda

  • Tokenning uch darajasini (primitive, semantic, component) React ilovasida qo'llaysiz.
  • Qorong'i rejimni faqat tokenlar bilan, komponentlarga tegmasdan yasaysiz.
  • Variantlarni data-variant va cva bilan xaritalaysiz; cva ichida nima borligini o'zingiz yozib ko'rasiz.
  • cn() (clsx + tailwind-merge) nega kerakligini tushuntira olasiz.
  • Figma → DTCG JSON → Style Dictionary → CSS zanjirini taniysiz va vazifalar ga tokenlarni kiritib, holat 33 ni commit qilasiz.

Oldin bilishingiz kerak: Komponent API'sini loyihalash, React'da stil berish usullari, Dizayn tokenlari, Qorong'i rejim va temalash.

1. Nega bu kerak?

Oldingi darsda fayllarni xususiyat bo'yicha tartibga soldik. Endi ko'rinishni tartibga solamiz.

vazifalar.css ni oching. Unda 0.25rem, 0.5rem, 0.75rem, 1rem qiymatlari 43 joyda qo'lda yozilgan: padding: 0.5rem 0.75rem, gap: 0.5rem, border-radius: 0.5rem, margin: 0.25rem 0 0.5rem… Dizayner keldi: "Telefonda hamma narsa siqilib turibdi, oraliqlarni biroz kattalashtiramiz." Qaysi 0.5rem oraliq, qaysi biri burchak? Qaysilari "kichik oraliq", qaysilari "o'rta"? Bu savollarga kod javob bermaydi — u faqat sonlarni biladi, qarorlarni emas.

Ikkinchi muammo — kelishmovchilik. Bitta tugmada padding: 0.5rem, boshqasida 0.45rem — kimdir ko'z bilan "moslab" qo'ygan. Ilova "deyarli bir xil" ko'rinadi, va aynan shu "deyarli" uni arzon ko'rsatadi.

Hayotiy o'xshatish: «Bahor» oshxonasining retseptlar daftari. "Bir siqim tuz", "ozgina zira" deb yozilgan bo'lsa, har oshpaz o'zicha qiladi — osh har kuni boshqacha. "1 choy qoshiq tuz" deb yozilsa — hamma bir xil o'lchaydi. Choy qoshiq — o'lchov birligi, tokenning o'zi. Retsept "ozgina" emas, "1 choy qoshiq" deydi.

2. Tokenlar

2.1 Uch daraja

Dizayn tokenlari darsida token darajalarini o'rgangan edingiz. React ilovasidagi ko'rinishi:

Daraja Nima deydi vazifalar da
Primitive (xom) "bu rang bor" #1f6f4a, 0.5rem — alohida nomlanmagan
Semantic (ma'noli) "bu rang nima uchun" --rang-asosiy, --rang-xato, --joy-2, --burchak
Component "bu komponentda nima" yo'q — Tugma semantik tokenlarni to'g'ridan-to'g'ri oladi

Komponentlar semantik tokenlarni ishlatadi: color: var(--rang-xato), #b3261e emas. Ertaga brend qizil rangni o'zgartirsa, bitta token o'zgaradi. Kichik ilovada primitive va component darajalari shart emas — vazifalar da ikkalasi ham yo'q. Katta dizayn tizimida esa uchala daraja ham bo'ladi: palitrada 10 xil yashil (primitive), ulardan "asosiy", "muvaffaqiyat" tanlanadi (semantic), va har komponent o'z tokenini oladi (--tugma-fon: var(--rang-asosiy)). Shunda tugmani alohida qayta bo'yash mumkin.

2.2 O'lchov shkalasi

Oraliq tokenlari ma'no bo'yicha emas (--joy-tugma-ichi), shkala bo'yicha nomlanadi:

css
:root {
  --joy-1: 0.25rem; /*  4 px */
  --joy-2: 0.5rem;  /*  8 px */
  --joy-3: 0.75rem; /* 12 px */
  --joy-4: 1rem;    /* 16 px */
  --joy-6: 1.5rem;  /* 24 px */
}

Raqam — 4 px qadamlar soni: --joy-3 = 3 × 4 = 12 px. Bu "4 px to'r" (4-point grid) — Material Design, Tailwind va ko'p dizayn tizimlarining asosi. Shkala ataylab cheklangan: 0.45rem uchun token yo'q, demak uni yozib bo'lmaydi — kelishmovchilik o'z-o'zidan yo'qoladi. --joy-5 ham yo'q, chunki ilovada 20 px ishlatilmagan. Token ehtiyoj bilan paydo bo'ladi, "har ehtimolga" emas.

2.3 Token — ma'noga, qiymatga emas

Qiymat bir xil bo'lsa ham, ma'no har xil bo'lishi mumkin. --joy-4 ham 1rem, shrift o'lchami 1rem ham. Lekin shrift o'lchamini var(--joy-4) bilan yozish — xato. Dizayner oraliqlarni kattalashtirsa, sarlavha shrifti ham beixtiyor kattalashadi.

vazifalar da tokenlarni kiritayotganda aynan shu tuzoqqa tushdik. Qiymatlarni qiymat bo'yicha (1rem → var(--joy-4)) almashtirishda bitta joy ma'nosiga qaramay almashib ketdi: #batafsil h3 { font-size: var(--joy-4); }. Ekranda farq yo'q edi (qiymat bir xil), shuning uchun testlar ham jim qoldi. Xatoni keyingi kod ko'rigida topdik va tuzatdik. To'g'risi — alohida shrift shkalasi (--shrift-asosiy: 1rem) yoki qiymatni o'z holida qoldirish. Kanonda ikkinchisi tanlandi: font-size: 1rem («Vazifalar qadami» bo'limida ko'rasiz).

Tekshirib ko'ring: --rang-qizil: #b3261e va --rang-xato: #b3261e. Qaysi birini Tugma ning xavfli varianti ishlatishi kerak va nega?

Javob

--rang-xato — semantik token: "xato va xavf rangi". --rang-qizil — primitive: "shu qizil". Agar brend ertaga xavfni to'q sariq bilan ko'rsatishga qaror qilsa, --rang-xato ning qiymati o'zgaradi va hamma xavfli tugmalar o'zgaradi. Primitive --rang-qizil esa qizilligicha qolishi kerak — uni boshqa joylar (masalan, "achchiq" belgisi) ishlatayotgan bo'lishi mumkin. Komponentlar ma'noga bog'lanadi.

3. Qorong'i rejim — faqat tokenlar

3.1 light-dark() va color-scheme

Qorong'i rejim darsidagi g'oya: komponentlar rang ma'nosi bilan ishlaydi, rejim esa ma'noning qiymatini almashtiradi. vazifalar da (17-qismdan beri):

css
@supports (color: light-dark(white, black)) {
  :root {
    color-scheme: light dark;
    --rang-matn: light-dark(#1f2a24, #e6efe9);
    --rang-fon: light-dark(#f7faf8, #14201a);
    --rang-asosiy: light-dark(#1f6f4a, #7fd1a5);
    --rang-xato: light-dark(#b3261e, #ffb4ab);
    /* … */
  }
  :root[data-tema="yorug"] { color-scheme: light; }
  :root[data-tema="qorongi"] { color-scheme: dark; }
}

light-dark(A, B) — element color-scheme i yorug' bo'lsa A, qorong'i bo'lsa B. data-tema — foydalanuvchi tanlovi (TemaKonteksti). Tizim sozlamasi esa color-scheme: light dark orqali o'zi ishlaydi. Komponentlarda bitta ham if (dark) yo'q.

3.2 Ikki rejim yonma-yon

color-scheme faqat :root da emas, istalgan elementda ishlaydi. Shuning uchun bitta sahifada ikkala rejimni yonma-yon ko'rsatish mumkin — dizayn tizimi sahifalari (va Storybook) shunday qiladi. Pastdagi natijada tugma CSS'i bir xil, faqat ota blokning color-scheme i har xil:

html
<style>
  :root {
    --joy-2: 0.5rem;
    --joy-3: 0.75rem;
    --burchak: 0.5rem;
    --bosish: 2.75rem;
  }
  .yorug { color-scheme: light; }
  .qorongi { color-scheme: dark; }
  .panel {
    --rang-matn: light-dark(#1f2a24, #e6efe9);
    --rang-yuza: light-dark(#ffffff, #1d2b24);
    --rang-chegara: light-dark(#c9d6cf, #3a4d43);
    --rang-asosiy: light-dark(#1f6f4a, #7fd1a5);
    --rang-asosiy-ustida: light-dark(#ffffff, #0d1a13);
    --rang-xato: light-dark(#b3261e, #ffb4ab);
    display: flex;
    flex-wrap: wrap;
    gap: var(--joy-2);
    padding: var(--joy-3);
    border-radius: var(--burchak);
    background: var(--rang-yuza);
    color: var(--rang-matn);
    font: 16px/1.5 system-ui, sans-serif;
    margin-bottom: var(--joy-2);
  }
  .tugma {
    min-height: var(--bosish);
    padding: var(--joy-2) var(--joy-3);
    border: 1px solid var(--rang-chegara);
    border-radius: var(--burchak);
    background: var(--rang-yuza);
    color: var(--rang-matn);
    font: inherit;
  }
  .tugma[data-variant="asosiy"] {
    border-color: var(--rang-asosiy);
    background: var(--rang-asosiy);
    color: var(--rang-asosiy-ustida);
  }
  .tugma[data-variant="xavfli"] {
    border-color: var(--rang-xato);
    color: var(--rang-xato);
  }
</style>
<div class="panel yorug">
  <button class="tugma">Bekor qilish</button>
  <button class="tugma" data-variant="asosiy">Qo'shish</button>
  <button class="tugma" data-variant="xavfli">O'chirish</button>
</div>
<div class="panel qorongi">
  <button class="tugma">Bekor qilish</button>
  <button class="tugma" data-variant="asosiy">Qo'shish</button>
  <button class="tugma" data-variant="xavfli">O'chirish</button>
</div>

Nimaga qarang: yuqori va pastki qatordagi tugmalarning HTML'i va CSS qoidalari bir xil. Farq faqat .yorug/.qorongi klassida — color-scheme. Qorong'i rejimda "asosiy" yashil ochroq (#7fd1a5), "xavfli" qizil esa pushtiroq (#ffb4ab). To'q fonda to'q rangli matn o'qilmaydi — kontrast saqlanishi uchun qorong'i qiymatlar ochroq tanlangan. "Tahrirlab ko'rish" bilan biror tokenni o'zgartiring — ikkala panel birga o'zgaradi. Ranglar vazifalar dagi haqiqiy qiymatlar.

4. Tokenlar React'da

4.1 Asosiy yo'l — CSS

Tokenlar CSS'da yashaydi va React komponentlar ularni klass yoki atribut orqali ishlatadi: <Tugma variant="xavfli"> → data-variant="xavfli" → CSS var(--rang-xato). Komponent rang qiymatini bilmaydi va bilishi shart emas. Tema almashganda ranglarni brauzer CSS o'zgaruvchisi orqali o'zi yangilaydi. React faqat data-tema atributini o'zgartiradi — ro'yxatdagi har bir qatorni qayta chizishi shart emas. Shuning uchun 10 000 vazifali ro'yxatda ham tema bir zumda almashadi.

4.2 JS'da token kerak bo'lsa

Ba'zan qiymat JavaScript'da kerak: <canvas> diagrammasi rangi, grafik kutubxonasi sozlamasi. Tokenni ikki joyda (CSS va JS) takrorlamang — CSS'dan o'qing:

js
const style = getComputedStyle(document.documentElement);
const primary = style.getPropertyValue("--rang-asosiy").trim();
// "#1f6f4a" yoki qorong'i rejimda "#7fd1a5"

Teskari yo'nalish — JS'dan CSS'ga dinamik qiymat: style={{ "--progress": "40%" }} va CSS'da width: var(--progress) (React'da stil berish usullari da ko'rgansiz). Bu token emas, holat: token — o'zgarmas qaror, --progress — har renderda yangi qiymat.

5. Variantlar — tizimli

5.1 data-variant — oddiy CSS loyihasi

#31 da Tugma variant ni data-variant atributiga aylantirdi, CSS esa atribut selektori bilan ranglarni tanladi. Bu — oddiy CSS loyihasi uchun yetarli va to'liq yo'l: variant = bitta atribut, ziddiyat imkonsiz, stillar CSS faylida. Radix ham holatni shunday beradi (data-state="open"), shuning uchun bu naqsh tanish bo'ladi.

5.2 Tailwind loyihasi: variant → klasslar

Tailwind'da (React'da stil berish usullari) stil — klasslar ro'yxati: "rounded-md px-3 py-2 bg-green-700 text-white". Variantlar ko'payganda klass satrlarini if lar bilan yig'ish tezda chalkashadi. CVA (class-variance-authority) bu xaritalashni e'lon qilinadigan obyektga aylantiradi:

ts
import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva("rounded-md font-medium", {
  variants: {
    variant: {
      oddiy: "border bg-white text-gray-900",
      asosiy: "bg-green-700 text-white",
      xavfli: "border border-red-700 text-red-700",
    },
    size: { kichik: "px-2 py-1 text-sm", orta: "px-3 py-2" },
  },
  compoundVariants: [
    { variant: "xavfli", size: "kichik", class: "font-bold" },
  ],
  defaultVariants: { variant: "oddiy", size: "orta" },
});

type ButtonProps = VariantProps<typeof buttonVariants>;
// { variant?: "oddiy" | "asosiy" | "xavfli" | null; size?: … }

To'rt qism: asos (har doim), variants (har o'q — union, #31 dagi kabi), compoundVariants (o'qlar kesishganda qo'shimcha qoida) va defaultVariants. VariantProps turni obyektdan o'zi chiqaradi — variantni qo'shsangiz, props turi ham o'zi yangilanadi.

5.3 cva ichida nima bor

cva sehr emas — kichik funksiya. Uning soddalashtirilgan variantini o'zimiz yozamiz:

js
function cva(base, config) {
  const { variants, compoundVariants = [], defaultVariants = {} } =
    config;
  return (props = {}) => {
    const chosen = { ...defaultVariants, ...props };
    const classes = [base];
    for (const axis in variants) {
      classes.push(variants[axis][chosen[axis]]);
    }
    for (const { class: extra, ...when } of compoundVariants) {
      const match = Object.entries(when).every(
        ([axis, value]) => chosen[axis] === value,
      );
      if (match) classes.push(extra);
    }
    return classes.filter(Boolean).join(" ");
  };
}

const buttonVariants = cva("tugma", {
  variants: {
    variant: {
      oddiy: "",
      asosiy: "tugma-asosiy",
      xavfli: "tugma-xavfli",
    },
    size: { kichik: "tugma-kichik", orta: "" },
  },
  compoundVariants: [
    { variant: "xavfli", size: "kichik", class: "qalin" },
  ],
  defaultVariants: { variant: "oddiy", size: "orta" },
});

console.log(buttonVariants());
console.log(buttonVariants({ variant: "asosiy" }));
console.log(buttonVariants({ variant: "xavfli", size: "kichik" }));

Konsolda:

text
tugma
tugma tugma-asosiy
tugma tugma-xavfli tugma-kichik qalin

Uch qadam: standartlar ustiga foydalanuvchi tanlovi ({ ...defaultVariants, ...props }), har o'q bo'yicha klass, keyin kesishma qoidalari. filter(Boolean) bo'sh satrlarni tashlaydi. Haqiqiy cva ham shu ishni qiladi, qo'shimcha ravishda boolean variantlar, massivli shartlar va class/className ikkalasini qo'llaydi. Paket juda kichik va React'ga bog'liq emas — istalgan framework'da ishlaydi.

tailwind-variants (tv) — o'xshash g'oya, lekin slotlar bilan: bitta chaqiruv bir nechta element uchun klass beradi (card.base(), card.title()). U Tailwind klasslar ziddiyatini ham o'zi yechadi — keyingi bo'lim mavzusi.

5.4 cn(): clsx + tailwind-merge

Komponent o'z klasslarini beradi, foydalanuvchi esa className bilan qo'shimcha beradi (#31 dagi qoida: className qo'shiladi). Oddiy CSS'da bu muammosiz. Tailwind'da esa ziddiyat chiqadi:

jsx
<Button className="px-6">Saqlash</Button>
// komponent: "px-3 py-2" + foydalanuvchi: "px-6"
// natija: class="px-3 py-2 px-6" — qaysi biri yutadi?

Javob — na birinchisi, na oxirgisi bo'yicha: CSS'da qaysi qoida Tailwind'ning CSS faylida keyinroq yozilgan bo'lsa, o'sha. class atributidagi tartib ahamiyatsiz (kaskad). Foydalanuvchi px-6 yozdi, lekin px-3 yutishi mumkin. tailwind-merge shuni yechadi: u Tailwind klasslarini taniydi va bir xil xususiyatga tegishlilarining oxirgisini qoldiradi — "py-2 px-6".

clsx — shartli klasslarni yig'adi: clsx("btn", isActive && "faol", { xira: isStale }). Ikkalasining birlashmasi — shadcn/ui odati:

ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Va komponentda: className={cn(buttonVariants({ variant, size }), className)}. Tailwind komponent naqshlari darsida cn() ni CSS tomonidan ko'rgan edingiz — bu yerda u React komponent API'sining bir qismi.

Tekshirib ko'ring: vazifalar da Tailwind yo'q. Tugma ning className birlashtirishiga tailwind-merge kerakmi?

Javob

Kerak emas. Oddiy CSS'da tugma va foydalanuvchi klassi (qoshish) bir xil xususiyat uchun "raqobat" qilmaydi — ular turli qoidalar. Ziddiyat bo'lsa, CSS'ning odatiy qoidalari (specificity, tartib) ishlaydi. Tailwind-merge esa faqat bir xil xususiyatni o'zgartiradigan utility klasslar ko'p bo'lganda kerak. Kanon «o'lchab qaror qilish» tamoyiliga amal qiladi: muammo yo'q — paket yo'q.

6. Figma'dan kodga

6.1 Zanjir

Katta jamoada tokenlarni dizayner yaratadi — Figma'da. Agar dasturchi ularni qo'lda ko'chirsa, ikki manba paydo bo'ladi va ular albatta ajralib ketadi. Shuning uchun zanjir avtomatlashtiriladi:

flowchart LR
  F["Figma Variables<br/>rejimlar: yorug', qorong'i"] --> J["tokens.json<br/>(W3C DTCG)"]
  J --> S[Style Dictionary]
  S --> C[tokens.css]
  S --> T[tokens.ts]
  S --> M["iOS / Android"]
  C --> R[React komponentlar]

Diagrammadagi asosiy narsa — markazdagi JSON fayl. U yagona haqiqat manbai: Figma uni yozadi, qolgan hamma narsa undan yasaladi. Veb, iOS va Android ilovalari bir xil rangni oladi.

6.2 DTCG formati

DTCG (W3C Design Tokens Community Group) — tokenlar uchun umumiy JSON formati. Uning birinchi barqaror versiyasi — 2025.10 — 2025-yil 28-oktyabrda chiqdi (manba: designtokens.org/tr/2025.10/format). Bu W3C standarti emas, hamjamiyat guruhining hisoboti. Lekin Figma, Style Dictionary, Tokens Studio kabi vositalar uni qo'llaydi:

json
{
  "rang": {
    "asosiy": {
      "$type": "color",
      "$value": "#1f6f4a",
      "$description": "Asosiy amallar: Qo'shish, Saqlash"
    }
  },
  "joy": {
    "2": { "$type": "dimension", "$value": "0.5rem" }
  }
}

$ bilan boshlangan kalitlar — format qismi: $value (qiymat), $type (turi), $description. Qolgan kalitlar — guruhlar va nomlar. Style Dictionary bu faylni o'qib, har platforma uchun natija chiqaradi: CSS uchun --rang-asosiy: #1f6f4a;, TypeScript uchun export const rangAsosiy = "#1f6f4a";. Figma'da yorug' va qorong'i rejimlar (modes) bor — ular CSS'da light-dark() yoki [data-tema] bloklariga aylanadi.

vazifalar da Figma yo'q — tokenlar to'g'ridan-to'g'ri CSS'da. Bu kichik loyiha uchun to'g'ri: zanjir dizayner bilan ishlaganda o'zini oqlaydi. Lekin token nomlari (--rang-asosiy, --joy-2) allaqachon shunday tanlangan-ki, ertaga ularni JSON'dan yasash qiyin bo'lmaydi.

7. Vazifalar qadami: dizayn tokenlari (holat 33)

refactor/tugma-tokenlar branch'idagi uchinchi va oxirgi qism. vazifalar.css boshiga yangi blok:

css
/* Oraliq, burchak, o'lcham tokenlari (18/#33). Rang tokenlari —
   pastda */
:root {
  --joy-1: 0.25rem;
  --joy-2: 0.5rem;
  --joy-3: 0.75rem;
  --joy-4: 1rem;
  --joy-6: 1.5rem;
  --burchak: 0.5rem;
  --burchak-katta: 0.75rem;
  /* Bosiladigan element balandligi: 44 px — WCAG 2.5.5 (tegish
     nishoni) */
  --bosish: 2.75rem;
  --soya: 0 0.25rem 1rem rgb(0 0 0 / 0.2);
}

Izohlar telefonga sig'ishi uchun bo'lindi. --bosish — barmoq bilan bosiladigan element balandligi: 44 px, WCAG 2.5.5 "Target Size" talabi (a11y). --soya — butun box-shadow qiymati bitta token: menyu ro'yxati va toast bir xil "ko'tarilgan".

Keyin 43 ta qiymat tokenga almashtirildi. Masalan, umumiy tugma qoidasi:

diff
 button, select {
-  min-height: 2.75rem;
-  padding: 0.5rem 0.75rem;
+  min-height: var(--bosish);
+  padding: var(--joy-2) var(--joy-3);
   border: 1px solid var(--rang-chegara);
-  border-radius: 0.5rem;
+  border-radius: var(--burchak);
   background: var(--rang-yuza);
   color: var(--rang-matn);
 }

Va #26 dagi menyu ro'yxati:

diff
-  margin-top: 0.25rem;
-  padding: 0.25rem;
+  margin-top: var(--joy-1);
+  padding: var(--joy-1);
   border: 1px solid var(--rang-chegara);
-  border-radius: 0.5rem;
+  border-radius: var(--burchak);
   background: var(--rang-yuza);
-  box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 0.2);
+  box-shadow: var(--soya);

Hamma almashtirishlarda hisoblangan qiymat o'sha: var(--joy-2) = 0.5rem. Ekranda bitta piksel ham o'zgarmaydi — bu refaktorlashning sharti. Buni brauzer tekshiruvi tasdiqladi: 159/159, e2e-18 50/50. Kelajakda oraliqlarni o'zgartirish — :root dagi bitta qatorni o'zgartirish.

Endi «Token — ma'noga, qiymatga emas» bo'limidagi tuzoq qanday tuzatilganini ko'ring. Statistika jadvali sarlavhasi:

diff
-#batafsil h3 { margin-bottom: 0.25rem; font-size: 1rem; }
+#batafsil h3 { margin-bottom: var(--joy-1); font-size: 1rem; }

margin-bottom — oraliq, shuning uchun --joy-1 oldi. font-size esa shrift o'lchami: qiymati 1rem bo'lsa ham, u oraliq shkalasiga tegishli emas va 1rem bo'lib qoldi. Endi dizayner --joy-4 ni kattalashtirsa, sarlavha shrifti joyida turadi.

Hammasi ham almashtirilmagan: dialog ichidagi 1.25rem yoki #filtrlar dagi gap: 0.5rem kabi bir nechta qiymat qoldi. Shkalada 20 px (1.25rem) yo'q. Qiymatni zo'rlab yaqin tokenga "tortish" ko'rinishni o'zgartirardi — bu esa refaktorlash emas.

Tugma variantlari (#31) — data-variant va semantik rang tokenlari. CVA kanonga kirmadi: Tailwind yo'q, uchta variant bitta atribut selektori bilan yechiladi, yangi paket kerak emas. Bu qaror 16-qismdagi Zod va 18/#21 dagi React Compiler qarorlari bilan bir qatorda: vosita muammo bo'lgandagina olinadi.

Holat 33 natijasi (natijalar/tekshiruv-33.txt): build, lint, format:check, tip, knip toza; testlar — node 170, Vitest 45; JS+CSS gzip — 89 719 B. Uch darsning umumiy diff'i: 21 fayl, +204 −93.

Commit:

text
refactor: Tugma (variant), dizayn tokenlari, statistika — xususiyat papkasi

Commit tanasida — uch qism: Tugma (#31: variant → data-variant, 9 joy, dialogda type="submit"), xususiyatlar/statistika/ + ochiq API + ESLint chegarasi (#32), tokenlar — --joy-*, --burchak*, --bosish, --soya (#33); va knip (5 topilma tuzatildi).

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Oraliqlar "deyarli" bir xil qo'lda yozilgan qiymatlar shkala tokenlari, ro'yxatdan tashqari qiymat yo'q
Oraliq o'zgardi — shrift ham o'zgardi qiymat bo'yicha token (font-size: var(--joy-4)) ma'no bo'yicha: alohida shrift tokenlari
Qorong'i rejimda matn o'qilmaydi komponentda xom rang (#1f2a24) semantik token + light-dark()
className="px-6" ishlamaydi (Tailwind) CSS tartibi, klass tartibi emas cn() = clsx + tailwind-merge
Figma va kod ranglari farq qiladi tokenlar qo'lda ko'chirilgan DTCG JSON + Style Dictionary
Har tema uchun komponentda if rang JS'da tanlanadi rang CSS'da, komponent faqat variant beradi

9. Mashqlar

Mashqlar kurs/mashqlar/18/33-tokenlar/ da. JavaScript yechimlarini Node'da ishga tushiring.

1-mashq (oson): qaysi daraja?

primitive, semantic yoki component deb yozing:

  • --yashil-700: #1f6f4a:
  • --rang-asosiy: var(--yashil-700):
  • --tugma-fon: var(--rang-asosiy):
  • --rang-xato:
Yechim

Primitive — palitradagi rang, nomi ko'rinishni aytadi ("yashil, 700-ton"). Semantic — ma'no ("asosiy", "xato"). Component — aniq komponent qismi ("tugma foni"). Har daraja keyingisiga havola qiladi: --tugma-fon → --rang-asosiy → --yashil-700 → #1f6f4a. Brend o'zgarsa — primitive, mavzu o'zgarsa — semantic, bitta komponent o'zgarsa — component darajasi o'zgaradi.

2-mashq (o'rta): cx — kichik clsx

clsx ga o'xshash cx(...args) yozing: satrlar qo'shiladi, false/null/undefined/"" tashlanadi, obyektda qiymati truthy kalitlar qo'shiladi, massivlar ichi ham xuddi shu qoida bilan ochiladi.

js
function cx(...args) {
  // shu yerga yozing
  return "";
}

const isStale = true;
console.log(cx("royxat", isStale && "eskirgan"));
console.log(cx("tugma", { faol: true, xira: false }, null));
console.log(cx(["badge", ["badge-yangi"]], undefined, ""));

Kutilgan natija:

text
royxat eskirgan
tugma faol
badge badge-yangi
Yechim
js
function cx(...args) {
  const out = [];
  for (const arg of args) {
    if (!arg) continue;
    if (typeof arg === "string") {
      out.push(arg);
    } else if (Array.isArray(arg)) {
      const inner = cx(...arg);
      if (inner) out.push(inner);
    } else if (typeof arg === "object") {
      for (const key in arg) if (arg[key]) out.push(key);
    }
  }
  return out.join(" ");
}

const isStale = true;
console.log(cx("royxat", isStale && "eskirgan"));
console.log(cx("tugma", { faol: true, xira: false }, null));
console.log(cx(["badge", ["badge-yangi"]], undefined, ""));

Konsolda:

text
royxat eskirgan
tugma faol
badge badge-yangi

!arg — false, null, undefined, "" va 0 ni bir yo'la tashlaydi. Massiv — rekursiya (Rekursiya): ichidagi massivlar ham shu funksiyaning o'zi bilan ochiladi. Haqiqiy clsx sonlarni ham qo'shadi (0 dan tashqari) — biz ularni o'tkazib yubordik. Bu funksiya klass ziddiyatini yechmaydi: cx("px-3", "px-6") — ikkalasi qoladi. Bu tailwind-merge'ning ishi.

3-mashq (qiyin): cva ga boolean variant

«cva ichida nima bor» bo'limidagi cva ga boolean variant qo'llashni qo'shing: variants: { loading: { true: "yuklanmoqda", false: "" } } bo'lsa, { loading: true } → yuklanmoqda klassi. JavaScript obyekt kalitlari satr bo'lgani uchun true → "true" ga aylantiring. Bundan tashqari, foydalanuvchi className bersa, u oxiriga qo'shilsin.

Ishora: String(value) va obyekt kaliti (obyekt kalitlari).

Kutilgan natija:

text
tugma tugma-asosiy yuklanmoqda
tugma qoshish
Yechim
js
function cva(base, { variants, defaultVariants = {} }) {
  return ({ className, ...props } = {}) => {
    const chosen = { ...defaultVariants, ...props };
    const classes = [base];
    for (const axis in variants) {
      const value = chosen[axis];
      if (value === undefined) continue;
      classes.push(variants[axis][String(value)]);
    }
    classes.push(className);
    return classes.filter(Boolean).join(" ");
  };
}

const buttonVariants = cva("tugma", {
  variants: {
    variant: { oddiy: "", asosiy: "tugma-asosiy" },
    loading: { true: "yuklanmoqda", false: "" },
  },
  defaultVariants: { variant: "oddiy", loading: false },
});

console.log(buttonVariants({ variant: "asosiy", loading: true }));
console.log(buttonVariants({ className: "qoshish" }));

Konsolda:

text
tugma tugma-asosiy yuklanmoqda
tugma qoshish

String(true) — "true": obyekt kalitlari doim satr, shuning uchun { true: "…" } yozuvi ham aslida "true" kaliti. className destrukturlash bilan ajratiladi — u variant emas, unga xaritalash kerak emas, va doim oxirida qo'shiladi. Haqiqiy cva ham class/className ni shunday qo'shadi. Tailwind'da esa natija baribir cn() dan o'tadi — ziddiyatlarni yechish uchun.

4-mashq: vazifalar qadami

vazifalar.css boshiga tokenlar blokini qo'shing va qiymatlarni almashtiring («Vazifalar qadami» bo'limi). Faqat ma'nosi oraliq, burchak yoki bosish balandligi bo'lgan qiymatlarni almashtiring. #batafsil h3 ning font-size iga --joy-4 qo'ymang — 1rem qoldiring yoki alohida shrift tokeni yarating. Brauzerda yorug' va qorong'i temani almashtirib ko'ring: hech narsa siljimasligi kerak. Hammasi toza bo'lsa — holat 33 commit'i va PR.

Yechim

"Hech narsa siljimadi" ni ko'z bilan emas, raqam bilan tekshiring. DevTools → Elements → Computed panelida bir nechta elementning padding va border-radius qiymatini o'zgarishdan oldin va keyin solishtiring — ular bir xil bo'lishi kerak. Avtomatik yo'l — brauzer tekshiruvi (159/159) va skrinshot solishtirish (keyingi qismlarda — vizual regressiya testlari). Shrift tokeni yaratsangiz, nomi shkala bo'yicha bo'lsin (--shrift-1, --shrift-2…) yoki ma'no bo'yicha (--shrift-matn, --shrift-sarlavha) — loyihada bitta uslub tanlang. Commit xabari — «Vazifalar qadami» bo'limida.

10. Real ishda

  • Dizayn tizimlari: Material Design 3, IBM Carbon, Shopify Polaris, GitHub Primer — hammasi tokenlar ustiga qurilgan va tokenlarini ochiq e'lon qiladi. Ularni o'qish — o'z tizimingizni loyihalashning eng yaxshi maktabi.
  • shadcn/ui: CSS o'zgaruvchilari (--primary, --destructive) + Tailwind + cva + cn() — bu darsdagi barcha qismlar bitta komponentda (shadcn/ui).
  • Dizayner bilan ishlash: "token nomi nima?" — dizayner va dasturchi o'rtasidagi umumiy til. Figma Variables va Style Dictionary bilan ishlash — frontend vakansiyalarida tez-tez so'raladigan ko'nikma.
  • Intervyu savollari: "Design token nima?", "Qorong'i rejimni qanday qilasiz?", "CVA nima uchun kerak?", "cn() ichida nima bor va nega tailwind-merge kerak?".

Xulosa

  • Token — nomli dizayn qarori; komponentlar semantik tokenlarni (--rang-xato, --joy-2) ishlatadi, xom qiymatni emas.
  • Oraliqlar — cheklangan shkala (4 px qadam); token ma'noga bog'lanadi, qiymatga emas.
  • Qorong'i rejim — faqat tokenlar: light-dark() + color-scheme, komponentda if (dark) yo'q.
  • Variantlar: oddiy CSS'da data-variant + atribut selektori; Tailwind'da cva/tailwind-variants va cn() (clsx + tailwind-merge).
  • Figma Variables → DTCG JSON → Style Dictionary → CSS/TS: tokenlarning yagona manbai.
  • vazifalar holat 33: --joy-*, --burchak*, --bosish, --soya — 43 qiymat, ekranda hech narsa o'zgarmadi; Tugma, xususiyat papkasi va tokenlar bitta commit'da.

Keyingi dars: React + TS I: props va children turlari — komponent props'larini TypeScript bilan to'liq tiplash.

Manbalar

  • W3C Design Tokens Community Group: "Design Tokens Format Module 2025.10" (2025-10-28) — designtokens.org/tr/2025.10/format/
  • Style Dictionary — styledictionary.com
  • class-variance-authority — cva.style/docs
  • tailwind-variants — tailwind-variants.org
  • tailwind-merge — github.com/dcastil/tailwind-merge
  • MDN: light-dark() — developer.mozilla.org/docs/Web/CSS/color_value/light-dark
  • WCAG 2.2: 2.5.5 Target Size — w3.org/WAI/WCAG22/Understanding/target-size-enhanced
  • Kanon: sinov/18/QADAMLAR.md (#31–#33), holatlar/33/src/vazifalar.css, natijalar/tekshiruv-33.txt
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Dizayn tizimi asoslari React'da: tokenlar va variantlar — IlmHamroh