{"id":"l-rf-4-2","courseId":"react-fundamentals","lang":"ar","kind":"lesson","summary":"أضف Tailwind CSS v4 إلى مشروع Vite عبر الـ plugin الرسمي، ونسّق Watchlist بالـ utility classes ومتغيّرات الحالة (variants)، وخصّص السمة داخل CSS.","html":"<p>Watchlist يعمل، لكنه يبدو كأنه من عام 1998. يمكنك كتابة stylesheet، وكثير من التطبيقات الممتازة تفعل ذلك. لكن هذه الدورة تستخدم <strong>Tailwind CSS</strong>، لأنه شائع جدًا في مشاريع React التي ستنضمّ إليها، ولأن التنسيق داخل الـ JSX مباشرة يُبقي الـ components مكتفية بذاتها: ملف <code dir=\"ltr\">MovieItem</code> يحمل الـ markup والسلوك والمظهر معًا.</p>\n<p>Tailwind مجموعة كبيرة من الـ utility classes الصغيرة، كلٌّ منها تفعل شيئًا واحدًا: <code dir=\"ltr\">p-4</code> تضيف حشوًا، و<code dir=\"ltr\">rounded-lg</code> تدوّر الزوايا، و<code dir=\"ltr\">text-slate-600</code> تضبط لونًا. تركّبها داخل <code dir=\"ltr\">className</code>. ويمسح Tailwind ملفاتك، ويعثر على الـ classes التي استخدمتها، ويولّد stylesheet لا يحتوي إلا عليها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ثبّته</h2>\n<p>لـ Tailwind v4 plugin رسمي لـ Vite، والإعداد ثلاث خطوات. ثبّت الحزم:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> install</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> tailwindcss</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> @tailwindcss/vite</span></span></code></pre></div>\n<p>أضف الـ plugin إلى إعدادات Vite:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">vite.config.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { defineConfig } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'vite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> react </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@vitejs/plugin-react'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> tailwindcss </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@tailwindcss/vite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineConfig</span><span style=\"color:#953800;--shiki-dark:#FFA657\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  plugins: [</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">react</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">tailwindcss</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()]</span><span style=\"color:#953800;--shiki-dark:#FFA657\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">})</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>واستورد Tailwind في أعلى ملف الأنماط الرئيسي، الذي يستورده <code dir=\"ltr\">main.jsx</code> أصلًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">src/index.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"tailwindcss\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>هذا كل شيء. لا يوجد <code dir=\"ltr\">tailwind.config.js</code> ولا إعدادات PostCSS في v4 افتراضيًا. يعثر Tailwind على ملفات المصدر بنفسه. ويعيد Vite تشغيل خادم التطوير تلقائيًا حين تحفظ <code dir=\"ltr\">vite.config.js</code>؛ وإن لم تظهر الأنماط بعد ذلك، فأوقفه وشغّل <code dir=\"ltr\">npm run dev</code> من جديد.</p>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الدروس القديمة</span></p>\n<p>إذا رأيت <code dir=\"ltr\">@tailwind base; @tailwind components; @tailwind utilities;</code> أو مصفوفة <code dir=\"ltr\">content: [...]</code> في ملف إعدادات، فهذا Tailwind v3. الأفكار تنتقل، لكن الإعداد أعلاه هو الحالي.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">قراءة الـ utility classes</h2>\n<p>هذا هو الهيكل الخارجي لـ Watchlist:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mx-auto max-w-xl px-4 py-10\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"text-3xl font-bold tracking-tight text-slate-900\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Watchlist&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mt-1 text-sm text-slate-500\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">left</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> to watch&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  {</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* …form, filters, list */</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>الأسماء مختصرة لكنها منهجية، وبعد يوم واحد ستقرؤها بسرعة قراءة CSS:</p>\n<ul>\n<li><strong>المسافات</strong> تستخدم مقياسًا كل خطوة فيه 0.25rem: <code dir=\"ltr\">p-4</code> حشو مقداره 1rem، و<code dir=\"ltr\">mt-1</code> هامش علوي مقداره 0.25rem، و<code dir=\"ltr\">px-4</code> حشو أفقي. و<code dir=\"ltr\">mx-auto</code> يوسّط الكتلة.</li>\n<li><strong>الأحجام والخطوط</strong>: <code dir=\"ltr\">max-w-xl</code> تحدّ العرض، و<code dir=\"ltr\">text-3xl</code> تضبط حجم الخط، و<code dir=\"ltr\">font-bold</code> سُمكه.</li>\n<li><strong>الألوان</strong> تأتي من لوحة بدرجات من 50 إلى 950: <code dir=\"ltr\">text-slate-500</code> و<code dir=\"ltr\">bg-indigo-600</code>.</li>\n</ul>\n<p>ويستطيع محرّرك المساعدة: إضافة Tailwind CSS IntelliSense الرسمية لـ VS Code تكمل أسماء الـ classes تلقائيًا وتعرض الـ CSS الذي تنتجه كل واحدة حين تمرّر المؤشّر عليها.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ variants: الشروط في بادئة</h2>\n<p>ضع بادئة قبل الـ class لتطبيقه تحت شرط فقط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">src/components/MovieItem.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flex items-center gap-3 rounded-lg border border-slate-200 bg-white p-3 dark:border-slate-700 dark:bg-slate-800\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.watched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'flex-1 text-slate-400 line-through'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'flex-1 text-slate-900 dark:text-slate-100'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    aria-pressed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.watched</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> onToggle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(movie.id)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    className</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"rounded-md border border-slate-300 px-3 py-1 text-sm hover:bg-slate-100 focus-visible:outline-2 focus-visible:outline-indigo-500 aria-pressed:border-indigo-600 aria-pressed:bg-indigo-600 aria-pressed:text-white\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">movie.watched </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'Mark watched'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">li</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<ul>\n<li>تتعامل <code dir=\"ltr\">hover:</code> و<code dir=\"ltr\">focus-visible:</code> مع حالات التفاعل. امنح العناصر التفاعلية دائمًا نمط تركيز مرئيًا لمستخدمي لوحة المفاتيح.</li>\n<li>تُطبَّق <code dir=\"ltr\">dark:</code> حين يكون نظام المستخدم في الوضع الداكن (تتبع <code dir=\"ltr\">prefers-color-scheme</code> افتراضيًا).</li>\n<li>تُطبَّق <code dir=\"ltr\">aria-pressed:</code> حين تكون <code dir=\"ltr\">aria-pressed</code> في العنصر true. فيتبع مظهر زر التبديل الخاصية نفسها التي تعلنها الـ screen readers، ولا يمكن أن يختلفا.</li>\n<li>نقاط التوقّف تبدأ من الجوّال: <code dir=\"ltr\">sm:</code> و<code dir=\"ltr\">md:</code> و<code dir=\"ltr\">lg:</code> تُطبَّق من ذلك العرض <strong>فما فوق</strong>، لذا اكتب تخطيط الهاتف أولًا، وأضف البادئات للشاشات الأكبر.</li>\n</ul>\n<p>أمّا الشروط المعتمدة على الـ state في React، مثل <code dir=\"ltr\">movie.watched</code> على الـ span، فهي معامل ثلاثي عادي يختار بين نصّين كاملين من الـ classes.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>بناء أسماء الـ classes من قطع</span></p>\n<p>يبدو <code dir=\"ltr\">className={`text-${color}-600`}</code> ذكيًا لكنه يفشل في الإنتاج. يقرأ Tailwind ملفاتك كنص، ولا يولّد إلا الـ classes التي يجدها مكتوبة كاملة؛ و<code dir=\"ltr\">text-red-600</code> لا تظهر أبدًا، فلا تُولَّد أبدًا. اربط القيم بـ classes كاملة بدلًا من ذلك: <code dir=\"ltr\">const tone = { ok: 'text-green-600', error: 'text-red-600' }[status];</code>.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ design tokens الخاصة بك</h2>\n<p>خصّص السمة داخل CSS باستخدام <code dir=\"ltr\">@theme</code>. كل متغيّر يصبح utilities تلقائيًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">src/index.css</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@import</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"tailwindcss\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@theme</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --color-brand-500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oklch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.62</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.19</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 285</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --color-brand-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">oklch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.55</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.21</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 285</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --font-display</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Inter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">system-ui</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">sans-serif</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الآن تعمل <code dir=\"ltr\">bg-brand-600</code> و<code dir=\"ltr\">text-brand-500</code> و<code dir=\"ltr\">font-display</code> كأنها classes مدمجة. عرّف عددًا قليلًا من الـ tokens للون علامتك التجارية وخطوطك، بدل أن تنثر قيمًا عابرة مثل <code dir=\"ltr\">bg-[#6d28d9]</code> في أنحاء الـ components.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">المقايضة</h2>\n<p>قوائم الـ classes الطويلة هي الثمن الصريح. قد يحمل زر منسَّق خمسة عشر class. والحلّ ليس إخفاءها في CSS باستخدام <code dir=\"ltr\">@apply</code>؛ بل ما تدرّبت عليه طوال الدورة: استخرج component. الـ component المسمّى <code dir=\"ltr\">Button</code> يحمل هذه الـ classes الخمسة عشر في مكان واحد، وفي كل مكان آخر تكتب <code dir=\"ltr\">&lt;Button&gt;</code>. الـ components هي وحدة إعادة الاستخدام في React، للأنماط تمامًا كما للسلوك.</p>\n<p>Tailwind ليس الجواب الجيد الوحيد. الـ CSS Modules (مدمجة في Vite: سمِّ الملف <code dir=\"ltr\">MovieItem.module.css</code> واستورده) تعطيك أسماء classes محصورة النطاق مع CSS عادي، وكثير من الفرق تفضّلها. والمهارات تنتقل في الحالتين: markup دلالي، ومقياس متّسق للمسافات والألوان، وحالات تركيز مرئية، وتخطيط يعمل على الهاتف أولًا.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>استخدم القيم الافتراضية أولًا</span></p>\n<p>مقياس المسافات ولوحة الألوان في Tailwind مصمّمان بعناية. الالتزام بهما يعطيك مظهرًا متّسقًا مجانًا. والجأ إلى القيم المخصّصة فقط حين يتطلّبها التصميم فعلًا.</p>\n</aside>\n<p>صار Watchlist يبدو كمنتج حقيقي. بقي شيء واحد: وضعه على الإنترنت، وهذا موضوع الدرس التالي.</p>\n","toc":[{"id":"sec-1","text":"ثبّته","level":2},{"id":"sec-2","text":"قراءة الـ utility classes","level":2},{"id":"sec-3","text":"الـ variants: الشروط في بادئة","level":2},{"id":"sec-4","text":"الـ design tokens الخاصة بك","level":2},{"id":"sec-5","text":"المقايضة","level":2}],"takeaways":["يحتاج Tailwind v4 إلى <code dir=\"ltr\">npm install tailwindcss @tailwindcss/vite</code>، والـ plugin داخل vite.config.js، و<code dir=\"ltr\">@import &quot;tailwindcss&quot;;</code> في ملف CSS؛ ولا حاجة إلى ملف إعدادات.","كل utility class تقابل تصريح CSS واحدًا على مقياس مشترك، مثل <code dir=\"ltr\">p-4</code> لحشو مقداره 1rem، و<code dir=\"ltr\">text-slate-600</code> للون من لوحة الألوان.","البادئات تطبّق الـ class بشرط، مثل <code dir=\"ltr\">md:</code> فوق نقطة توقّف، و<code dir=\"ltr\">hover:</code> عند التمرير، و<code dir=\"ltr\">dark:</code> في الوضع الداكن، و<code dir=\"ltr\">aria-pressed:</code> حين تكون تلك الخاصية true.","يعثر Tailwind على الـ classes بمسح ملفاتك كنص، لذا اكتب دائمًا أسماء classes كاملة ولا تبنِها أبدًا بدمج النصوص.","خصّص الألوان والخطوط وغيرها من الـ tokens بمتغيّرات CSS داخل كتلة <code dir=\"ltr\">@theme</code>."],"further":[{"title":"Installing Tailwind CSS with Vite","url":"https://tailwindcss.com/docs/installation/using-vite"},{"title":"Styling with utility classes","url":"https://tailwindcss.com/docs/styling-with-utility-classes"},{"title":"Detecting classes in source files","url":"https://tailwindcss.com/docs/detecting-classes-in-source-files"},{"title":"Theme variables","url":"https://tailwindcss.com/docs/theme"}],"quiz":[{"q":"<p>ثبّتّ الحزمتين وأضفت <code dir=\"ltr\">@import &quot;tailwindcss&quot;;</code> إلى <code dir=\"ltr\">src/index.css</code>، لكن لا تأثير لأي utility class. ما الخطوة الناقصة على الأرجح؟</p>","options":[{"text":"إنشاء ملف <code dir=\"ltr\">tailwind.config.js</code>.","why":"يعمل Tailwind v4 من دون ملف إعدادات؛ فهو يكتشف ملفات المصدر تلقائيًا."},{"text":"إضافة ملف إعدادات لـ PostCSS.","why":"مع Vite، يحلّ الـ plugin المسمّى <code dir=\"ltr\">@tailwindcss/vite</code> محلّ إعداد PostCSS. لا تحتاج إلى الاثنين."},{"text":"إعادة تشغيل الحاسوب.","why":"إعادة تشغيل خادم التطوير قد تفيد بعد تغيير الإعدادات، لكن الحاسوب ليس المشكلة."},{"text":"إضافة <code dir=\"ltr\">tailwindcss()</code> من <code dir=\"ltr\">@tailwindcss/vite</code> إلى مصفوفة <code dir=\"ltr\">plugins</code> في vite.config.js.","why":"صحيح. من دون الـ plugin يعامل Vite سطر الاستيراد كـ CSS عادي ولا يولّد الـ utilities أبدًا."}],"answer":3},{"q":"<p>يبني component اسم class بالشكل <code dir=\"ltr\">`text-${color}-600`</code>، حيث <code dir=\"ltr\">color</code> قيمتها ‘red’ أو ‘green’. في الإنتاج لا لون للنص. لماذا؟</p>","options":[{"text":"يمسح Tailwind الملفات كنص عادي، فلا يرى <code dir=\"ltr\">text-red-600</code> أو <code dir=\"ltr\">text-green-600</code> مكتوبة كاملة، ولا يولّدها أبدًا.","why":"صحيح. اربط القيم بأسماء classes كاملة بدلًا من ذلك، مثل <code dir=\"ltr\">{ red: 'text-red-600', green: 'text-green-600' }[color]</code>."},{"text":"الـ template literals غير مسموح بها في <code dir=\"ltr\">className</code>.","why":"أي نص يعمل في className. المشكلة أن Tailwind لا يستطيع رؤية اسم الـ class النهائي حين يبني الـ CSS."},{"text":"الدرجة 600 غير موجودة للأحمر والأخضر.","why":"لكل لون في اللوحة الافتراضية درجات من 50 إلى 950، بما فيها 600."},{"text":"نسخ الإنتاج تحذف كل classes الـ Tailwind القادمة من الـ props.","why":"لا يعرف Tailwind شيئًا عن الـ props. إنه يحتفظ فقط بالـ classes التي تظهر كاملة في مكان ما من ملفات المصدر."}],"answer":0},{"q":"<p>ماذا يفعل <code dir=\"ltr\">className=&quot;bg-white md:bg-slate-100 dark:bg-slate-900&quot;</code>؟</p>","options":[{"text":"يطبّق الخلفيات الثلاث معًا، والأخيرة تفوز.","why":"الـ classes ذات البادئات لا تُطبَّق إلا تحت شروطها؛ إنها لا تتراكم ببساطة."},{"text":"أبيض على الشاشات الصغيرة، وslate-100 من نقطة التوقّف md فما فوق، وslate-900 في الوضع الداكن.","why":"صحيح. بادئات نقاط التوقّف تبدأ من الجوّال (min-width)، و<code dir=\"ltr\">dark:</code> تتبع تفضيل المستخدم لنظام الألوان افتراضيًا."},{"text":"إنه غير صحيح؛ لا يمكنك استخدام إلا class خلفية واحد لكل عنصر.","why":"يمكنك الجمع بين ما تشاء من الـ classes؛ والـ variants موجودة تحديدًا لتكون للعنصر الواحد أنماط مختلفة في ظروف مختلفة."}],"answer":1},{"q":"<p>تضبط أزرار الفلترة لديك <code dir=\"ltr\">aria-pressed</code>. ما أنظف طريقة لتنسيق الزر النشط باستخدام Tailwind؟</p>","options":[{"text":"احسب الـ class في JavaScript بالشكل <code dir=\"ltr\">`bg-${active ? 'indigo' : 'white'}-600`</code>.","why":"هذا يبني اسم class ديناميكيًا، وهو ما لا يستطيع Tailwind اكتشافه. ولا حاجة إليه هنا أصلًا."},{"text":"استخدم <code dir=\"ltr\">style={{ background: … }}</code> لأن Tailwind لا يستطيع التفاعل مع الـ state.","why":"لدى Tailwind variants لهذا بالضبط. كما أن الأنماط المضمّنة تخسر التعامل مع التمرير والتركيز والوضع الداكن."},{"text":"استخدم الـ variant المسمّى <code dir=\"ltr\">aria-pressed:</code>، مثل <code dir=\"ltr\">aria-pressed:bg-indigo-600 aria-pressed:text-white</code>.","why":"صحيح. يتبع التنسيق خاصية إمكانية الوصول التي ضبطتها أصلًا، فلا يمكن أن تختلف الحالة المرئية عمّا تعلنه الـ screen readers."}],"answer":2}],"exercise":{"kind":"fill","language":"js","template":"// Terminal\n// npm install {{0}} @tailwindcss/vite\n\n// vite.config.js\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport tailwindcss from '{{1}}';\n\nexport default defineConfig({\n  plugins: [react(), {{2}}],\n});\n\n/* src/index.css */\n@import \"{{3}}\";\n\n// src/components/FilterButton.jsx: style the active filter\n<button aria-pressed={active} className=\"px-3 py-1 {{4}}:bg-indigo-600\">","blanks":[{"answers":["tailwindcss"]},{"answers":["@tailwindcss/vite"]},{"answers":["tailwindcss()"]},{"answers":["tailwindcss"]},{"answers":["aria-pressed"]}],"title":"اربط Tailwind v4","prompt":"<p>املأ الفراغات لإضافة Tailwind CSS v4 إلى مشروع Vite الخاص بـ Watchlist، ثم نسّق زر الفلترة النشط باستخدام الخاصية الموجودة عليه أصلًا.</p>\n","hints":["حزمتان: إطار العمل نفسه والـ plugin الرسمي الخاص به لـ Vite.","الـ plugin دالة تستدعيها داخل مصفوفة <code dir=\"ltr\">plugins</code>، بجانب <code dir=\"ltr\">react()</code>.","لدى Tailwind variants مسمّاة بأسماء خصائص ARIA. والزر يضبط <code dir=\"ltr\">aria-pressed</code>."],"explanation":"<p>يضيف <code dir=\"ltr\">npm install tailwindcss @tailwindcss/vite</code> إطار العمل والـ plugin الخاص به لـ Vite. يُستورد الـ plugin من <code dir=\"ltr\">@tailwindcss/vite</code> ويُستدعى بالشكل <code dir=\"ltr\">tailwindcss()</code> داخل <code dir=\"ltr\">plugins</code>، والسطر <code dir=\"ltr\">@import &quot;tailwindcss&quot;;</code> في ملف CSS الرئيسي يجلب الأنماط المولَّدة. لا حاجة إلى <code dir=\"ltr\">tailwind.config.js</code> في v4. ويطبّق الـ variant المسمّى <code dir=\"ltr\">aria-pressed:</code> الـ class <code dir=\"ltr\">bg-indigo-600</code> فقط حين تكون <code dir=\"ltr\">aria-pressed</code> في الزر true، فتتبع الحالة المرئية الخاصية نفسها التي تقرؤها التقنيات المساعدة.</p>\n","checkLabels":{}},"words":662,"readMinutes":4}