{"id":"l-ds-3-2","courseId":"design-systems","lang":"ar","kind":"lesson","summary":"تعرّف على انفجار الـ props واستبدله بمكوّنات مركّبة (compound components) تتشارك الحالة عبر الـ context، مع معرفة متى يبقى المكوّن الصغير المُعَدّ بالـ props هو الخيار الأفضل.","html":"<p>كان للإصدار الأول من ShipmentCard في Northwind ستة props. وبعد عام صار له ثلاثة وعشرون: <code dir=\"ltr\">title</code>، <code dir=\"ltr\">subtitle</code>، <code dir=\"ltr\">status</code>، <code dir=\"ltr\">statusTone</code>، <code dir=\"ltr\">showMenu</code>، <code dir=\"ltr\">menuItems</code>، <code dir=\"ltr\">footerText</code>، <code dir=\"ltr\">footerAction</code>، <code dir=\"ltr\">footerActionVariant</code>، <code dir=\"ltr\">hideBorder</code>، <code dir=\"ltr\">compact</code>، <code dir=\"ltr\">imageUrl</code>، <code dir=\"ltr\">imagePosition</code>، وهكذا. كل بطاقة مختلفة قليلًا لدى أحد الفرق صارت prop إضافيًا. وتحوّل كود المكوّن إلى أدغال من الشروط، وصارت صفحة التوثيق تسرد props لا يستطيع أحد أن يحفظها. هذا هو انفجار الـ props (prop explosion)، وكل نظام تصميم يقابله.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الإعدادات مقابل الـ composition</h2>\n<p>المكوّن <strong>المُعَدّ بالإعدادات</strong> (configured) يستقبل البيانات ويقرّر التخطيط بنفسه. أما المكوّن <strong>المركّب</strong> (composed) فيقدّم قطعًا، والمستخدم يرتّبها.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 240\" role=\"img\" aria-labelledby=\"l-ds-3-2-t1\"><defs><marker id=\"arrow-l-ds-3-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ds-3-2-t1\">على اليسار: صندوق ShipmentCard واحد تغذّيه قائمة طويلة من الـ props. على اليمين: صندوق Card يحتوي قطع Header وBody وFooter يرتّبها المستخدم.</title>\n  <text class=\"d-label-strong\" x=\"150\" y=\"28\" text-anchor=\"middle\">الإعدادات</text>\n  <rect class=\"d-box\" x=\"20\" y=\"44\" width=\"110\" height=\"170\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"30\" y=\"70\">title</text>\n  <text class=\"d-code\" x=\"30\" y=\"94\">statusTone</text>\n  <text class=\"d-code\" x=\"30\" y=\"118\">menuItems</text>\n  <text class=\"d-code\" x=\"30\" y=\"142\">footerText</text>\n  <text class=\"d-code\" x=\"30\" y=\"166\">hideBorder</text>\n  <text class=\"d-label-muted\" x=\"30\" y=\"196\">+18 أخرى</text>\n  <path class=\"d-arrow\" d=\"M130 129 L176 129\" marker-end=\"url(#arrow-l-ds-3-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"180\" y=\"94\" width=\"110\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"235\" y=\"134\" text-anchor=\"middle\">ShipmentCard</text>\n  <text class=\"d-label-strong\" x=\"500\" y=\"28\" text-anchor=\"middle\">الـ Composition</text>\n  <rect class=\"d-box-primary\" x=\"390\" y=\"44\" width=\"220\" height=\"180\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"404\" y=\"68\">Card</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"80\" width=\"180\" height=\"36\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"103\" text-anchor=\"middle\">Card.Header</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"124\" width=\"180\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"151\" text-anchor=\"middle\">Card.Body</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"176\" width=\"180\" height=\"36\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"199\" text-anchor=\"middle\">Card.Footer</text>\n</svg><figcaption>مكوّن واحد بـ props كثيرة، مقابل قطع صغيرة يرتّبها المستخدم</figcaption></figure>\n<p>هذه بطاقة الشحنة نفسها، مركّبة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">RouteCard.tsx</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\">Card</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\">Card.Header</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\">Card.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Shipment NW-4471&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Title</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\">Badge</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"info\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>In transit&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Badge</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\">ShipmentMenu</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> shipmentId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"NW-4471\"</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\">Card.Header</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\">Card.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Rotterdam to Hamburg · 2 pallets&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Body</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\">Card.Footer</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sm\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Track&#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\">Button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sm\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"primary\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Assign driver&#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\">Card.Footer</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\">Card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>لم تحتج البطاقة قط إلى prop باسم <code dir=\"ltr\">footerSecondaryAction</code>، لأن <code dir=\"ltr\">Card.Footer</code> هو slot يقبل أي إجراءات. والقائمة مكوّن خاص بالفريق، موضوع حيث تسمح الترويسة. ومع ذلك ما زال النظام يملك المسافات والحدود والطباعة والسلوك المتجاوب لكل قطعة. تحصل الفرق على مرونة في <em>ما يوضع وأين</em>، ويحتفظ النظام بالسيطرة على <em>شكل كل قطعة</em>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">مشاركة الحالة بين القطع</h2>\n<p>القطع الثابتة مثل Card لا تحتاج إلا إلى التخطيط. أما التفاعلية مثل Tabs فتحتاج إلى حالة مشتركة: التبويب المحدّد، وأيّ لوحة تُعرض. والطريقة المتينة لمشاركتها هي الـ context:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Tabs.tsx</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\"> { createContext, use, useId, useState } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react'</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> TabsContext</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  select</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  baseId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">|</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">null</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useTabs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> ctx</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> use</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(TabsContext);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ctx) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Tabs.* must be used inside &#x3C;Tabs>'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ctx;</span></span>\n<span class=\"line\"><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\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Tabs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">defaultValue</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">defaultValue</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> React</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ReactNode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useState</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(defaultValue);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> baseId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">TabsContext</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ selected, select, baseId }</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:#1F2328;--shiki-dark:#E6EDF3\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">TabsContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">Tabs.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">Trigger</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Trigger</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> React</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ReactNode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">baseId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useTabs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</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\">      type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      role</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tab\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">baseId</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}-tab-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      aria-selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">selected </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      aria-controls</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">baseId</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}-panel-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</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\"> select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">children</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\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Tabs.List (role=\"tablist\") and Tabs.Panel (role=\"tabpanel\") follow the same idea.</span></span></code></pre></div>\n<p>يقرأ الـ trigger الحالة المشتركة أينما كان في الشجرة: داخل tooltip، أو <code dir=\"ltr\">div</code> للتخطيط، أو مكوّن كتبه الفريق. في React 19 تعرض الـ context مباشرة كـ provider ‏(<code dir=\"ltr\">&lt;TabsContext value={…}&gt;</code>) وتقرؤه بـ <code dir=\"ltr\">use</code>. أدوار ARIA مأخوذة من نمط التبويبات في WAI-ARIA، وستتعرّف عليه جيدًا في الدرس التالي؛ أما التعامل مع لوحة المفاتيح فمحذوف هنا لإبقاء المثال قصيرًا.</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>ربط المكوّنات المركّبة بفحص الأبناء</span></p>\n<p>في تقنية أقدم يمرّ الأب على <code dir=\"ltr\">children</code> عبر <code dir=\"ltr\">React.Children.map</code> ويحقن الـ props في كل trigger. تنجح في العرض التوضيحي، وتنكسر أول مرة يغلّف فيها فريق trigger بـ tooltip، لأن الأب صار يرى الـ tooltip لا الـ trigger. استخدم الـ context؛ فهو لا يهتمّ بعمق القطع ولا بما يغلّفها.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">للـ composition تكاليفها أيضًا</h2>\n<p>الـ composition ليست مجانية. يكتب المستخدمون markup أكثر، ويمكنهم ترتيب القطع بطرق لم تقصدها، مثل تذييلين أو عنوان داخل المتن. تتعامل Northwind مع هذا بثلاث طرق. كل قطعة مقيّدة: <code dir=\"ltr\">Card.Footer</code> يرتّب الإجراءات، ولا شيء آخر يبدو صحيحًا فيه. والترتيبات الشائعة تُطلَق كوصفات موثّقة تنسخها الفرق. وبعض التركيبات الشائعة جدًا تصبح مغلّفات صغيرة مُعدّة بالـ props ومبنية <em>من</em> القطع المركّبة، مثل <code dir=\"ltr\">ShipmentSummaryCard</code> بأربعة props، فتبقى حالة الـ 80% قصيرة، وتبقى حالة الـ 20% ممكنة.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الانتقال بعيدًا عن مكوّن مُعَدّ بالإعدادات</h2>\n<p>نادرًا ما تحظى ببداية جديدة. كان ShipmentCard مستخدمًا في 60 شاشة حين أعدنا تصميمه. لم نحذفه؛ بل أعدنا بناءه <em>فوق</em> القطع الجديدة، فصارت الـ props الثلاثة والعشرون تعرض <code dir=\"ltr\">Card.Header</code> و<code dir=\"ltr\">Card.Body</code> و<code dir=\"ltr\">Card.Footer</code> داخليًا. استمرّت كل شاشة موجودة في العمل وحصلت على الأنماط الجديدة مجانًا. ثم علّمنا ShipmentCard كـ deprecated، ووثّقنا المقابل المركّب لكل تركيبة props شائعة، ونقلنا الشاشات فريقًا بعد فريق. بناء الـ API القديم من القطع الجديدة هو ما جعل الانتقال مملًّا، وهذا أعلى مديح يمكن أن يناله انتقال.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">متى تكون الإعدادات هي الصواب</h2>\n<p>ليس كل مكوّن يجب أن يكون مركّبًا. الـ Badge الخاص بالحالة له <code dir=\"ltr\">tone</code> وتسمية؛ والـ Avatar له صورة واسم وحجم. تنويعاتهما قليلة ومغلقة، فتصفهما الـ props وصفًا كاملًا، ولن تضيف الـ composition إلا تعقيدًا شكليًا.</p>\n<p>قاعدة عملية من مراجعات الـ API في Northwind: حين تبدأ الـ props بوصف <strong>التخطيط</strong> (<code dir=\"ltr\">imagePosition</code>، <code dir=\"ltr\">footerAlign</code>) أو <strong>بنية المحتوى</strong> (<code dir=\"ltr\">menuItems</code>، <code dir=\"ltr\">footerText</code>)، فالمكوّن يريد slots أو قطعًا. وحين تصف الـ props <strong>المظهر أو الحالة</strong> (<code dir=\"ltr\">tone</code>، <code dir=\"ltr\">size</code>، <code dir=\"ltr\">disabled</code>)، فالإعدادات لا بأس بها.</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>في مراجعة الـ API لا تقيّم طلب اليوم وحده. اسأل الفريق صاحب الطلب كيف تبدو البطاقات المشابهة في أماكن أخرى من منتجه. إن استطعت أن تتخيّل عشرة props أخرى قادمة، فصمّم الـ slot الآن.</p>\n</aside>\n<p>في التمرين تحكم على اقتراح API لـ Dialog بهذه القواعد. وبعدها تتأكّد أن كل قطعة من هذه القطع تعمل مع لوحة المفاتيح وقارئ الشاشة (screen reader)، افتراضيًا.</p>\n","toc":[{"id":"sec-1","text":"الإعدادات مقابل الـ composition","level":2},{"id":"sec-2","text":"مشاركة الحالة بين القطع","level":2},{"id":"sec-3","text":"للـ composition تكاليفها أيضًا","level":2},{"id":"sec-4","text":"الانتقال بعيدًا عن مكوّن مُعَدّ بالإعدادات","level":2},{"id":"sec-5","text":"متى تكون الإعدادات هي الصواب","level":2}],"takeaways":["حين تضيف كل حالة استخدام جديدة prop جديدًا، فالمكوّن يُعَدّ من الخارج وسيظلّ يكبر؛ أما الـ composition (التركيب) فتسمح للمستخدمين بترتيب القطع بأنفسهم.","المكوّنات المركّبة مثل <code dir=\"ltr\">Card.Header</code> و<code dir=\"ltr\">Tabs.Trigger</code> تتشارك الحالة عبر الـ context، لا عبر فحص الأبناء (children).","يظلّ النظام مالكًا لشكل كل قطعة وسلوكها، فتضيف الـ composition مرونة دون التخلّي عن الاتّساق.","المكوّنات الصغيرة المغلقة مثل Badge أو Avatar لا بأس أن تُعَدّ بالـ props؛ والجأ إلى الـ composition حين تبدأ الـ props بوصف التخطيط أو المحتوى."],"further":[{"title":"Passing data deeply with context (React)","url":"https://react.dev/learn/passing-data-deeply-with-context"},{"title":"use (React API reference)","url":"https://react.dev/reference/react/use"},{"title":"Tabs pattern (WAI-ARIA Authoring Practices)","url":"https://www.w3.org/WAI/ARIA/apg/patterns/tabs/"}],"quiz":[{"q":"<p>طلب أحد الفرق prop باسم <code dir=\"ltr\">footerSecondaryActionIcon</code> على ShipmentCard. علامَ يدلّ هذا الطلب على الأرجح؟</p>","options":[{"text":"أن البطاقة تحتاج prop واحدًا إضافيًا، ثم ستكتمل.","why":"كل prop «أخير» يتبعه آخر؛ والنمط يُظهر أن الـ API لا نهاية طبيعية له."},{"text":"أن على الفريق أن يبني بطاقته الخاصة.","why":"النسخة المنفصلة تفقد أنماط النظام وإصلاحاته؛ المشكلة في شكل API البطاقة، لا في الفريق."},{"text":"أن البطاقة تُعَدّ من الخارج، فتتحوّل قرارات التخطيط والمحتوى باستمرار إلى props.","why":"صحيح. slot للتذييل يقبل أي إجراءات كان سيستوعب هذا الطلب والطلبات العشرة التالية."},{"text":"أن مجموعة الأيقونات ينقصها أيقونة.","why":"الطلب يتعلّق بمكان المحتوى، لا بالأيقونات الموجودة."}],"answer":2},{"q":"<p>كيف يجب أن يعرف <code dir=\"ltr\">Tabs.Trigger</code> أيّ تبويب هو المحدّد؟</p>","options":[{"text":"بقراءته من context يوفّره <code dir=\"ltr\">Tabs</code>.","why":"صحيح. الـ context يعمل مهما كان عمق تداخل الـ trigger، وأيًّا كان الترتيب الذي يستخدمه المستخدم."},{"text":"بأن يمرّ <code dir=\"ltr\">Tabs</code> على أبنائه عبر <code dir=\"ltr\">React.Children.map</code> ويحقن الـ props.","why":"هذا ينكسر بمجرّد أن يُغلَّف الـ trigger بعنصر آخر، مثل tooltip أو div للتخطيط."},{"text":"بأن يقرأ كل trigger متغيّرًا عامًا (global).","why":"مجموعتا تبويبات في صفحة واحدة ستتنازعان على المتغيّر نفسه."},{"text":"بأن يمرّر المستخدم <code dir=\"ltr\">selected</code> لكل trigger يدويًا.","why":"ممكن، لكنه يلقي إدارة حالة المكوّن نفسه على كل فريق."}],"answer":0},{"q":"<p>أيّ مكوّن هو الأنسب ليبقى مكوّنًا بسيطًا يُعَدّ بالـ props؟</p>","options":[{"text":"Dialog فيه ترويسة ومتن وتذييل ولوحة جانبية اختيارية.","why":"محتواه وتخطيطه يتفاوتان كثيرًا بين الاستخدامات، وهذا بالضبط حيث تساعد الـ composition."},{"text":"جدول بيانات بخلايا مخصّصة وأشرطة أدوات وإجراءات للصفوف.","why":"الجداول تتفاوت تفاوتًا هائلًا؛ والجداول المبنية على الإعدادات وحدها تصبح أطول قوائم props في أي نظام."},{"text":"Badge لعرض الحالة بـ <code dir=\"ltr\">tone</code> وتسمية قصيرة.","why":"صحيح. تنويعاته قليلة ويمكن عدّها، فبضعة props تصفه وصفًا كاملًا."}],"answer":2}],"exercise":{"kind":"choice","multiple":false,"options":["props","children-only","compound","per-team"],"correct":["compound"],"title":"اختر الـ API الخاص بالـ Dialog","prompt":"<p>تحتاج Northwind إلى Dialog. الاستخدامات المعروفة حتى الآن: تأكيد حذف (عنوان وجملة وزرّان)، ونموذج إسناد سائق فيه قائمة قابلة للبحث، ومعاينة مسار فيها خريطة ولوحة جانبية، ونموذج اشتراك في النشرة البريدية للتسويق. وصلت أربعة اقتراحات للـ API. اختر الاقتراح الذي توافق عليه.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TSX</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:#6E7781;--shiki-dark:#8B949E\">// A</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Delete route?\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> body</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"…\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> confirmLabel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Delete\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> confirmVariant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"danger\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        cancelLabel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Cancel\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> showSidePanel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onConfirm</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:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// B</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onOpenChange</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">setOpen</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:#1F2328;--shiki-dark:#E6EDF3\">anything</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// C</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onOpenChange</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">setOpen</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:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Delete route?&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Header</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\">Dialog.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>…&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Body</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\">Dialog.Footer</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">close</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Cancel&#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\">Button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"danger\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">remove</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Delete&#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\">Dialog.Footer</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\">Dialog</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// D</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// No shared Dialog: each team builds its own on top of the native &#x3C;dialog> element.</span></span></code></pre></div>\n","hints":["عدّد مدى اختلاف الاستخدامات الأربعة المعروفة. أيّ API يستوعب خريطة مع لوحة جانبية دون props جديدة؟","ثم اسأل أيّ خيار ما زال يسمح للنظام بامتلاك إدارة التركيز والمسافات والاسم الذي يُعرِّف به العنوانُ النافذةَ لتقنيات الوصول."],"explanation":"<p>الاقتراح C يعتمد الـ composition: النظام يملك الطبقة المعتمة وإدارة التركيز والمسافات والعنوان الذي يسمّي النافذة، بينما ترتّب الفرق محتوى الترويسة والمتن والتذييل بحرية. أما A فيناسب تأكيد الحذف ويتحوّل إلى انفجار props من حالة الاستخدام الثانية. وB مرن لكنه يتخلّى عن البنية، فيحلّ كل فريق مسائل العناوين والمسافات والتذييلات بطريقته. وD يرمي عمل النظام في إمكانية الوصول بالكامل. ويمكن لاحقًا بناء مكوّن صغير مُعَدّ بالـ props باسم <code dir=\"ltr\">ConfirmDialog</code> فوق C للحالة الأكثر شيوعًا.</p>\n","checkLabels":{},"optionText":{"props":{"text":"A: Dialog واحد يُعَدّ بالكامل عبر الـ props.","why":"يناسب التأكيد ويفشل في معاينة الخريطة؛ وكل استخدام جديد يصبح prop إضافيًا مثل <code dir=\"ltr\">showSidePanel</code>."},"children-only":{"text":"B: Dialog يعرض أيّ أبناء يستقبلهم.","why":"مرن إلى أقصى حدّ، لكن النظام لم يعد يملك العناوين والمسافات والتذييلات، فتتباعد النوافذ من جديد."},"compound":{"text":"C: Dialog مركّب من قطع Header وTitle وBody وFooter.","why":"صحيح. يغطّي الاستخدامات الأربعة كلها بينما يحتفظ النظام بالسيطرة على البنية والأنماط وإمكانية الوصول."},"per-team":{"text":"D: لا مكوّن مشترك؛ تستخدم الفرق عنصر <code dir=\"ltr\">&lt;dialog&gt;</code> الأصلي مباشرة.","why":"العنصر الأصلي أساس جيد لـ Dialog الخاص بالنظام، لكن ترك كل فريق ينسّقه ويسمّيه بنفسه يعيد إنتاج التناقض الذي وُجد النظام لإزالته."}}},"words":612,"readMinutes":4}