تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

التوثيق

أدلة التصميم

استخدم هذا الدليل قبل اختيار المكوّنات أو كتابة كود التخطيط لتطبيق مبني بـ Native Shell. يسجّل الدليل الحكم المنتجي الذي راكمه GPUI Kit عبر سنوات من العمل على سطح المكتب، وقد انتقل إلى NSH: ينبغي أن تبدو الواجهة أصيلة للمنصة، ومتحفظة، ودقيقة، ومفهومة دون تخمين.

هذا دليل معياري. تدل كلمة يجب على قيد يتعلق بالصحة أو بالنظام البيئي، وتدل كلمة ينبغي على الخيار الافتراضي الذي يحتاج تجاوزه إلى سبب محدد، وتدل كلمة يمكن على تقنية اختيارية. تبقى صفحتا Elements وAPI المرجع المعتمد للخصائص والدوال الفردية.

تبني القواعد على السلوك في nsh/base (nsh_base في Rust)، وعلى السمة ورموزها، وعلى التفاعل الذي يعرفه مستخدمو كل منصة أصلًا. يعرض NSH الواجهة بعناصر واجهة المستخدم الخاصة بكل منصة ويترك المظهر للتطبيق: تقدّم مكوّنات nsh/base السلوك والحالة والتركيز وإمكانية الوصول، بينما يأتي كل لون وحجم ونصف قطر وتباعد من مكوّنات التطبيق نفسه، وعليه فإن هذه القواعد توجّه نظام التصميم الخاص بالتطبيق. تقدّم shadcn/ui أساليب مفيدة (الشيفرة المفتوحة، والتركيب، والإعدادات الافتراضية الموثوقة) لكنها لا تحدد كيف ينبغي أن يبدو تطبيق NSH. عند تعارض المؤثرات، حافظ على سلوك المنصة وعلى التفاعل الذي يفهمه الناس أصلًا.

#أطروحة التصميم

ابنِ واجهات تبدو أصيلة للمنصة، وهادئة، ودقيقة. دع المحتوى والتسلسل الهرمي والتفاعل يحملون التجربة؛ ينبغي أن تدعمها الزخرفة لا أن تنافسها.

  1. الوضوح قبل الشخصية. اجعل المهمة الأساسية والإجراء التالي واضحين قبل إضافة التعبير عن العلامة التجارية.
  2. التركيب قبل الابتكار. ابدأ بالمكوّنات الراسخة وركّبها في مسارات عمل خاصة بالمنتج. لا تنشئ عنصرًا أوليًا جديدًا إلا عندما يكون سلوكه جديدًا فعلًا.
  3. الرموز قبل القيم. ينبغي أن تشكّل الألوان وأنصاف الأقطار والخطوط والتباعد نظامًا واحدًا. تجنّب القيم الحرفية المعزولة التي لا تستجيب للسمات. راجع التنسيق وTailwind والسمة.
  4. المنصة قبل أعراف الويب. حافظ على الوصول بلوحة المفاتيح، وإطار النافذة، والقوائم، وعروض البيانات الكثيفة، والمناطق القابلة لتغيير الحجم، والتنقل الدائم على أجهزة سطح المكتب، وعلى ترويسات النظام وإيماءة الرجوع والتنبيهات وأهداف اللمس على الهواتف والأجهزة اللوحية، حيثما تستفيد المهمة منها.
  5. يجب أن تكون الحالة مرئية. تحتاج حالات التمرير والضغط والتركيز والتحديد والتعطيل والتحميل والتحقق والإجراءات المدمّرة إلى معالجة متمايزة ومتسقة.
  6. الهندسة توضّح الاحتواء. ينبغي أن تصف أنصاف الأقطار والهوامش الداخلية والحدود تسلسلًا هرميًا واحدًا متماسكًا، حتى حيث تتداخل الأسطح.
  7. التغذية الراجعة تعكس الواقع. ينبغي أن توضّح حالات التحميل والتلميحات والحركة حالةً أو انتقالًا حقيقيًا، لا أن تضيف مراسم إلى إجراء فوري.

#التعلّم من Shadcn

أهم ما تقدّمه Shadcn ليس لونًا بعينه للحدود، بل طريقة لبناء نظام:

  • امتلك الطبقة العليا من الواجهة بدلًا من مصارعة تجريد مغلق؛
  • ركّب أجزاءً صغيرة يمكن التنبؤ بها في مكوّنات خاصة بالمنتج؛
  • وفّر إعدادات افتراضية تشكّل أصلًا لغة بصرية واحدة؛
  • أبقِ الشيفرة والتركيب مقروءين للناس وللذكاء الاصطناعي على حد سواء؛
  • افصل العناصر الأولية للسلوك عن الطبقة المنسّقة.

يطبّق NSH هذه الأفكار من خلال الفصل بين العناصر الأولية في nsh، والمكوّنات الأساسية غير المنسّقة في nsh/base، والطبقة المنسّقة التي يملكها التطبيق (الطبقات الثلاث). تبني التطبيقات مكوّناتها على nsh/base، بـ TSX أو في حزمة Rust الخاصة بالتطبيق؛ ويعود السلوك القابل لإعادة الاستخدام إلى nsh/base، وتبقى السياسة البصرية فوقه.

لا تنسخ افتراضات الويب هذه دون تمحيص:

عادة الويبالافتراضي في NSH
مؤشر اليد المشيرة على كل زرالمؤشر الافتراضي؛ واليد المشيرة (cursor-pointer) للروابط
التنقل بين الصفحات بوصفه البنية الأساسيةنوافذ وألواح وأشرطة جانبية وتبويبات وقوائم دائمة على أجهزة سطح المكتب؛ وNavStack مع زر الرجوع الخاص بالنظام على الهواتف
التركيز والتمرير في المتصفح كحل احتياطيملكية صريحة للتركيز وتمرير تملكه المنطقة (Scroll، List)
عمود واحد يبدأ من الجوالعمود واحد على الهواتف، وواجهة قابلة لتغيير الحجم بحد أدنى محدد لحجم النافذة على أجهزة سطح المكتب
إجراءات حرجة تظهر عند التمرير فقطإجراءات يمكن الوصول إليها بلوحة المفاتيح واللمس والمؤشر ولا تعتمد على التمرير
صف من أزرار الأيقونات التي تظهر عند التمرير فقطإجراء أساسي مرئي مع Menu أو ContextMenu للأوامر الثانوية
نص بنمط الرابط لأوامر التطبيقButton بنمط التطبيق الافتراضي أو المحدد بإطار أو الشبحي؛ ونمط الرابط فقط لعناوين URL أو موارد الويب أو عناوين البريد الإلكتروني
كثافة لمس كبيرة في كل مكانأهداف بحجم اللمس على شاشات اللمس؛ وكثافة متوسطة مع المؤشر، ومضغوطة فقط حيث يستفيد منها العمل المعلوماتي
ترويسة مرسومة وبطاقة تنبيه على كل منصةترويسة النظام وAlertDialog على iOS وAndroid؛ وخانة header الخاصة بالصفحة على أجهزة سطح المكتب والويب
تجاوزات CSS عبر العناصر الفرعيةمكوّنات التطبيق وأجزاؤها ورموز السمة

#ابدأ من المهمة

قبل رسم أي شاشة، دوّن ما يلي:

  • مهمة المستخدم الأساسية؛
  • الكائن الذي يجري عرضه أو تغييره؛
  • الإجراءات التي يجب أن تبقى متاحة فورًا؛
  • المعلومات اللازمة لاتخاذ قرار؛
  • حالات الفراغ والتحميل والخطأ وعدم الاتصال والقراءة فقط ورفض الإذن؛
  • مسار لوحة المفاتيح واللمس وقارئ الشاشة عبر سير العمل؛
  • مكان عرض التطبيق: بمفرده أو داخل تطبيق مضيف.

نظّم الشاشة حول هذه الإجابات. لا تبدأ بشبكة لوحة معلومات أو بفهرس مكوّنات. تعرض الواجهة الجيدة النموذج الذهني للمستخدم: المستندات أو الحسابات أو المشاريع أو الرسائل أو الإعدادات أو أي كائن مستقر آخر، لا البنية الداخلية للخدمات.

صمّم المهمة الأساسية قبل توزيع عناصر التحكم. ينبغي أن يتناسب ثقلها البصري وموضعها وعمق معلوماتها مع أهميتها للمنتج. يجب ألا تُختزل النتيجة الجوهرية في عدد صغير أو أيقونة في زاوية أو إجراء ضعيف في التذييل بينما يستهلك المحتوى الثانوي الصفحة. عندما تكون مجموعة النتائج هي القيمة الرئيسية للمنتج، فكّر في منطقة ملخص أو بطاقة تعرض العدد ونتائج تمثيلية وحالة ذات معنى وإجراءً تاليًا واضحًا.

لكل إجراء مقترح، سمِّ الكائن المرئي الذي يخصه وحالته الحالية ونطاقه ونتيجته. إذا لم تُظهر الواجهة هذه الأمور أو لم تدل عليها بوضوح، فالإجراء سابق لأوانه. لا تعرض قدرة ما لمجرد أن الواجهة الخلفية تملكها؛ صمّم أولًا كيف يدخل الكائن إلى النموذج الذهني للمستخدم.

#اللغة البصرية

#التسلسل الهرمي

فضّل عددًا قليلًا من المستويات الواضحة:

  • عنوان النافذة أو الصفحة يحدد الكائن الحالي أو مساحة العمل؛
  • عنوان القسم يفصل المناطق ذات المعنى (role="header"، ليتمكن قارئو الشاشة من التنقل بينها)؛
  • نص المتن يحمل العمل؛
  • النص الخافت يقدّم البيانات الوصفية الثانوية والمساعدة؛
  • التسميات تعرّف عناصر التحكم والقيم.

استخدم الحجم والوزن والتباعد والفواصل قبل إضافة اللون أو الحاويات. تجنّب تداخل البطاقات داخل بطاقات: تحتاج معظم المناطق إلى خلفية وحدّ رفيع وتباعد مقصود فقط.

قيّم التسلسل الهرمي على مستوى الميزة كلها، لا مكوّنًا بمكوّن. أخفِ لون التمييز والزخرفة أثناء المراجعة: ينبغي أن تبقى المهمة الأساسية والتحديد الحالي وملخص النتائج والإجراء التالي واضحة من البنية وحدها. قد تفشل الشاشة التي تضم عناصر تحكم معقولة كل على حدة إذا لم تشكّل ترتيب قراءة واحدًا ومسار قرار واحدًا.

تعامل مع التأكيد بوصفه ميزانية محدودة. يحتاج السطح المحلي إلى نقطة تركيز واحدة واضحة، لا إلى حقل من التمييزات المتنافسة. إذا كان كل شيء ملونًا أو موسومًا بشارة أو غامقًا أو محاطًا بإطار أو مرفوعًا إلى تنبيه، فلن يبدو شيء مهمًا. حدّد الأولوية بالبنية والقرب أولًا؛ واصرف الألوان الأقوى والمكوّنات الأثقل فقط حيث يغيّر التمييز ما يلاحظه المستخدم أو ما يفعله.

#اللون والسمات

اقرأ الألوان من السمة (theme() في Rust، وفئات الرموز في TSX) واستخدمها بحسب الدور الدلالي:

  • background وforeground للسطح الرئيسي والنص؛
  • card وpopover وsidebar ورموز المقدمة الخاصة بها للأسطح المسماة لها؛
  • muted وmuted-foreground للمعلومات الداعمة؛
  • primary للإجراء الأساسي أو تأكيد التحديد، وsecondary وaccent لتأكيد أهدأ وللصفوف الممرَّر فوقها أو المركَّز عليها؛
  • destructive فقط للإجراءات المدمّرة والأخطاء، وألوان الحالة التي يضيفها التطبيق إلى سمته (success، warning) لمعانيها فقط؛
  • chart-1 إلى chart-5 لسلاسل البيانات؛
  • border وinput وring للبنية والتفاعل.
use nsh_base::theme;

view()
    .rounded(move || theme().radius)
    .border_1()
    .border_color(move || theme().border)
    .bg(move || theme().background)
    .p_4()
    .gap_1()
    .child(text("Balance").text_color(move || theme().foreground))
    .child(text("Updated just now").text_sm().text_color(move || theme().muted_foreground))

لا تستخدم لون حالة دلاليًا للزخرفة. لا تنقل المعنى باللون وحده. تحقق من كل سطح مخصص في السمتين الفاتحة والداكنة ومع قيم السمة المخصصة، بما في ذلك سمة التطبيق المضيف الذي يضم تطبيقك؛ ولا تفترض أبدًا أن المقدمة سوداء أو أن الخلفية بيضاء.

استخدم الشارة لحالة قصيرة أو عدد أو تصنيف يستفيد من المسح السريع، لا لكل تسمية أو قيمة وصفية أو مرشّح أو عنوان قسم. أبقِ معظم الشارات محايدة؛ واحتفظ بالألوان الدلالية للحالات التي تحمل فعلًا معنى النجاح أو التحذير أو التدمير أو الإعلام. غالبًا ما يدل صف من الشارات متعددة الألوان على غياب قرار في التسلسل الهرمي أو التجميع.

يجب ألا تحتوي واجهة التطبيق على ألوان hex خام أو rgb()/rgba() أو hsl()، ولا على فئات لوحات الألوان مثل bg-zinc-100. حلّ الألوان من السمة بحسب الدور الدلالي. وإذا لم يكن الدور المطلوب موجودًا، فعرّفه في ملف سمة التطبيق (رموز السمة: يؤدي "up": "#16a34a" في light وdark إلى text-up وbg-up/30 وtoken("up") في TSX وtheme().color("up") في Rust) بدلًا من تضمين قيمة لوحة ألوان في موضع الاستدعاء. لا مكان للألوان الخام إلا داخل تعريفات السمة أو في محتوى بيانات/نقطي مدقَّق يكون لونه نفسه هو البيانات.

#نصف القطر والتباعد والكثافة

استخدم سلمًا محدودًا لأنصاف الأقطار، كما هو الحال مع الخطوط وأحجام عناصر التحكم. اختر من radius في السمة (لعناصر التحكم والأسطح على حد سواء، rounded-(--radius))، ومن أنصاف الأقطار التي يضيفها التطبيق إلى سمته، ومن rounded-sm … rounded-2xl في Tailwind بينهما) بدلًا من ابتكار نصف قطر لكل عنصر. يمكن للحاويات الكبيرة أن تستخدم درجة أنعم من عناصر التحكم التي تحتويها؛ أما عناصر التحكم المدمجة والطبقات المنبثقة فتستخدم درجات أضيق. بالنسبة إلى المستطيل العادي، ينبغي أن يبقى نصف القطر أصغر بوضوح من نصف ضلعه الأقصر. استخدم rounded-full (.rounded_full()) فقط للدوائر والأقراص المقصودة؛ يجب ألا يتحول التلميح إلى كبسولة عن غير قصد.

ينبغي أن تُقرأ الأسطح المتداخلة بوصفها أشكالًا متحدة المركز. استخدم inner radius ≈ max(0, outer radius − gap) لاختيار أنسب نصف قطر؛ ويجب أن يبقى النطاق المرئي متساويًا عبر الانحناء. أضف رمزًا مسمى إلى السمة ("radius-inset": "0.375rem"، rounded-(--radius-inset)) إذا احتاج تركيب متكرر إلى قيمة وسطية. التحكم المجزأ ذو صورة ظلية واحدة: تتبع أجزاؤه الطرفية الزوايا الداخلية للحاوية، وتبقى الأجزاء الوسطى مربعة، ويكون لكل حد فاصل واحد بسماكة ثابتة.

Light theme chat composer: the inset surface, both buttons, and placeholder spacing change together; red guide circles show their shared corner center. Dark theme chat composer: the inset surface, both buttons, and placeholder spacing change together; red guide circles show their shared corner center.

يجب أن يحكم نصف القطر السطح المرئي بأكمله، لا حدّه فقط. تبقى الزوايا المكشوفة شفافة لتُظهر السطح الذي خلفها؛ ويجب أن تتبع خلفيات العناصر الفرعية وتعبئات التحديد والمحتوى القابل للتمرير الحدّ نفسه. امنح الأسطح الداخلية نصف قطر خاصًا بها، أو اقتطع الحاوية بـ overflow-hidden (.overflow_hidden())، ثم افحص التركيب على خلفية متباينة في السمتين. حافظ على حلقات التركيز الخارجية عند اختيار القص.

استخدم سلمًا مدمجًا للتباعد وكرّره. ينبغي أن تكون أزواج التسمية/عنصر التحكم المترابطة أقرب من المجموعات المنفصلة؛ وأن تكون المجموعات المنفصلة أقرب من الأقسام المنفصلة. فضّل أحجام مكوّنات التطبيق (صغير، ومتوسط افتراضي، وكبير) على الارتفاعات المخصصة لمرة واحدة. استخدم الأنماط المدمجة لأشرطة الأدوات والشاشات كثيفة البيانات، لا لحشر تخطيط غير واضح في مساحة أقل.

السلم الدلالي المشترك صغير عمدًا: يتدرج التباعد تقريبًا عبر 2 و4 و8 و12 و16 و24 و32 نقطة، بينما تبقى الخطوط قرب 12 و14 و16 و18 و20 نقطة. تعامل مع هذه الأرقام بوصفها علاقات، لا إذنًا بنثر قيمها الحالية في شيفرة الميزات. يوفّرها سلم التباعد في Tailwind: الخطوة الواحدة 4 نقاط (p-4 يساوي 16 نقطة)، في الفئات والدوال المكتوبة في Rust على حد سواء؛ ويغيّر "spacing" في "tailwind" الخاص بـ nsh.json خطوة الفئات، بينما تحتفظ الدوال المكتوبة بسلم Tailwind الافتراضي. أما التطبيق الذي يحتاج إلى سلم مختلف فيملكه في مكوّناته أو رموز سمته (p-(--gap-card)) ويستخدمه باتساق.

#القواعد المكانية

تعبّر المسافات عن العلاقات. اختر فجوة من المقياس الدلالي بسؤال نفسك عمّا يعنيه العنصران أحدهما للآخر:

العلاقةالفئة المعتادةالمقياس الحاليأمثلة
التصحيح البصريgap-0.52 ptخط الأساس للأيقونة، فاصل مدمج
أجزاء عنصر تحكم واحدgap-14 ptأيقونة/تسمية القائمة، العنوان/الوصف
عناصر تحكم وثيقة الصلةgap-28 ptأيقونة/تسمية الزر، إجراءات مربع الحوار
مجموعة محتوى واحدةgap-312 ptأعمدة الإشعار، صفوف النماذج المدمجة
مجموعات منفصلة في قسم واحدgap-416 ptحشوة اللوحة، مجموعات النموذج
أقسام منفصلةgap-624 ptالكتل الرئيسية في صفحة أو في لوحة فحص
حدود منطقة رئيسيةgap-832 ptمساحة التنفس في الحالة الفارغة، نطاقات الصفحة

تصف هذه القيم المقياس الافتراضي، وليست قيمًا حرفية تُكرَّر. استخدم فئات المسافات والدوال ذات الأنواع (gap-2، .gap_2()، p-4، .p_4()) أو رموز السمة (tokens). وينبغي أن يحافظ أي مقياس يملكه المنتج على الترتيب والعلاقات نفسها، وأن يُمرَّر عبر مكوّنات التطبيق الخاصة به بدلًا من كتابته عند كل موضع استدعاء.

استخدم هذه القواعد عند تحديد المسافات الأفقية والرأسية:

  1. الداخل قبل الخارج. حشوة المكوّن تخصّه، أما الفجوة بين المكوّنات فتخص المكوّن الأب.
  2. الإيقاع الرأسي يُظهر التجميع. الفجوة بين العنوان ووصفه أصغر من الفجوة بين ذلك الوصف والقسم التالي. تساوي الفجوات يعني تساوي العلاقات.
  3. المسافة الأفقية تدعم المسح البصري. تُبقي الصفوف المتكررة الأيقونات والتسميات والقيم والشارات والإجراءات الطرفية في أعمدة ثابتة.
  4. البداية والنهاية مفهومان دلاليان. فكّر في حواف اتجاه القراءة واكتبها بصيغة start وend (ps-*، me-*، .ps_4())، لينعكس التخطيط من اليمين إلى اليسار.
  5. لا تضاعف الحشوة. لا ينبغي أن تضيف البطاقة الموضوعة داخل لوحة ذات حشوة أصلًا هامشًا كاملًا آخر للوحة تلقائيًا.
  6. استخدم المحاذاة البصرية باعتدال. التصحيح بمقدار نقطة أو نقطتين مقبول لهندسة الأيقونة أو الرمز، لكن وثّق سبب اختلافه عن المقياس.

توضّح التركيبات الشائعة هذه العلاقات:

  • تستخدم محتويات الزر 4 pt في الأحجام الصغيرة و8 pt في الأحجام العادية؛
  • تستخدم رؤوس مربعات الحوار وتذييلاتها فجوة داخلية مقدارها 8 pt؛
  • تستخدم صفوف القوائم المدمجة وصفوف القوائم المنسدلة حشوة رأسية 4 pt وحشوة أفقية 8–12 pt؛
  • تستخدم رؤوس الأوراق السفلية (sheets) مسافة بادئة تقارب 16 pt في جهة البداية و12 pt في جهة النهاية، لتترك مساحة لعنصر الإغلاق، بينما تستخدم التذييلات 16 pt أفقيًا و12 pt رأسيًا؛
  • تستخدم الإشعارات حشوة أفقية 16 pt وفجوة أعمدة 12 pt لأن الأيقونة والرسالة والإجراء مجموعات متمايزة.

لا تتعامل مع هذه القيم على أنها وصفات جاهزة للنسخ واللصق لكل سطح. إنها تكشف عن منطق النظام: عناصر التحكم أكثر إحكامًا من الداخل، والصفوف مُحسَّنة للمسح البصري، والحاويات تنفق مساحة عند حدودها أكثر مما تنفق بين محتوياتها.

#النِّسَب وتراتب الطبقات

ابدأ بمتطلبات المحتوى، ثم حدّد النِّسَب. تجنّب التقسيم إلى نصفين اعتباطًا عندما يكون لأحد الألواح دور مختلف بوضوح.

  • ينبغي أن يكون الشريط الجانبي للتنقل عريضًا بما يكفي لتبقى التسميات ثابتة، لكنه أقل بروزًا بوضوح من منطقة العمل. امنحه حدًّا أدنى وعرضًا مفضّلًا وحدًّا أقصى (min-w-*، w-*، max-w-*) بدلًا من الاكتفاء بنسبة مئوية.
  • في تخطيطات الرئيسي–التفصيلي (master–detail)، اترك المجموعة قابلة للمسح البصري وامنح لوحة التفاصيل المساحة الفائضة. كثيرًا ما تفيد نقطة بداية تقارب الثلث/الثلثين، لكن قيود المحتوى هي المرجع. وعلى الهاتف يصبح الجزءان صفحتين في NavStack.
  • لا ينبغي أن تغطي لوحات الفحص والأوراق المساعدة الكائن الأساسي افتراضيًا. ينبغي أن تكون قابلة لتغيير الحجم أو الإغلاق عندما يكبر محتواها؛ وSheet يُغلق بخلفيته وبمفتاح Escape وبزر الرجوع.
  • يأتي عرض مربع الحوار من القرار المطلوب: AlertDialog لتأكيد قصير، أو نموذج متوسط، أو نافذة/صفحة مخصّصة للعمل المعقّد. لا توسّع مربع الحوار لمجرد إيجاد مساحة بيضاء.
  • خصّص أقوى ارتفاع (elevation) لطبقة القرار العليا. وداخل الطبقة الواحدة، استخدم الخلفية والخطوط الدقيقة، لا ظلالًا أكبر تدريجيًا، لإظهار التراتب.

حدّد ثلاثة قيود للحجم لكل منطقة رئيسية: الحد الأدنى الذي تظل عنده المهمة قابلة للإنجاز، والقيمة الافتراضية المريحة، وكيفية استهلاك المساحة الفائضة. احفظ التقسيمات التي يتحكم بها المستخدم عندما تمثل تفضيلًا في سير العمل، وقيّد القيم المستعادة بحسب النافذة الحالية.

#تفاصيل المحاذاة

المحاذاة نظام بنيوي، وليست مرحلة صقل أخيرة. أنشئ مجموعة صغيرة من محاور المحاذاة لكل سطح: حواف بداية ونهاية مشتركة، وخطوط أساس للنصوص، وخطوط مركزية، ومسارات وظيفية ثابتة. ينبغي أن ترتبط العناصر في المستوى نفسه بالمحور نفسه من الأعلى إلى الأسفل أو من البداية إلى النهاية، حتى لو كانت من أنواع مكوّنات مختلفة.

Alignment spines across a content region Alignment spines across a content region

تظهر الخطوط الحمراء الرأسية بجانب الحواف المشتركة أو مراكز عناصر التحكم للمحتوى والحالة والوقت والإجراءات الطرفية. وتظهر الخطوط الأفقية أسفل خطوط أساس النصوص أو مارّةً بمركز الصف لإظهار المحاذاة السفلية والمحاذاة إلى المركز الرأسي. وتعزل المقارنة المدمجة انحرافًا بمقدار بكسل واحد مرسوم يجب تصحيحه عند المكوّن المالك للبنية.

  • امنح المناطق الشقيقة هامشًا داخليًا مشتركًا للمحتوى. ينبغي ألا يخترع العنوان وشريط الأدوات وصف القائمة والحالة الفارغة والتذييل التي تصف المستوى نفسه، كلٌّ منها، حافة بداية مختلفة قليلًا.
  • كرّر هندسة الأعمدة عبر المنطقة كلها. ينبغي أن تحجز الرؤوس والصفوف والملخصات وحالات التحميل والمحررات المضمّنة المسارات نفسها للهوية والبيانات الوصفية والحالة والأرقام والإجراءات.
  • حاذِ عناصر التحكم ذات الصلة عبر الصفوف والأقسام. ينبغي أن تُظهر تسميات النماذج وحقولها وأوصافها ورسائل التحقق شبكة رأسية ثابتة عند مسح الصفحة من الأعلى إلى الأسفل.
  • حافظ على تماسك النطاقات الأفقية. ينبغي أن تستخدم العناصر المشتركة في شريط أدوات أو رأس أو شريط حالة أو صف خط أساس واحدًا أو خطًّا مركزيًا واحدًا بدلًا من إزاحات مضبوطة لكل عنصر على حدة.
  • لا تُدخل مسافة بادئة إلا لتراتب حقيقي أو احتواء أو إفصاح تدريجي. المسافة البادئة الزخرفية تجعل العناصر الشقيقة تبدو تابعة وتكسر خط القراءة في السطح.
  • عند انتهاء مستوى متداخل، عُد بدقة إلى محور الأب. لا تدع الحشوة المتراكمة تنزاح عبر الحاويات المتداخلة.
  • حافظ على المحور عند غياب المحتوى الاختياري. ينبغي ألا يؤدي غياب الأيقونات أو الشارات أو الأوصاف أو الإجراءات الطرفية إلى تحريك التسميات المتبقية؛ استخدم خانات أو مسارات مقصودة عندما تكون المقارنة بين الصفوف مهمة.
  • حاذِ المناطق الرئيسية بعضها مع بعض حيثما يتطابق تراتبها. لا يلزم أن تتشارك رؤوس الشريط الجانبي وعناوين المحتوى واللوحات المقسّمة وأشرطة الأدوات والأشرطة السفلية كل الإحداثيات، لكن ينبغي أن تشكّل المستويات المتطابقة خطوطًا متصلة مرئية.

ليست كل الحواف مطالَبة بالمحاذاة. يمكن للعنصر الابن أن يُزاح، ولقيمة أساسية أن تتقدّم بياناتها الوصفية الداعمة، ولقرار مدمِّر أن يُفصل عمّا حوله. ويجب أن تعبّر هذه الاستثناءات عن تراتب أو معنى؛ ولا يجوز أن تنتج عن حشوة مكوّنات غير منسّقة. ابدأ بالمحور المشترك، ثم اجعل الاستثناء صريحًا.

عامِل المحاذاة الدقيقة والفجوات المتكررة بوصفها ثوابت جودة. عندما يُقصد بحافتين أو مسافتين أن تتساويا، فإن اختلافًا بمقدار بكسل واحد مرسوم يُعد عيبًا، لا تقريبًا بصريًا مقبولًا. افحص الحدود المحسوبة بأداة قياس عند أحجام نوافذ وأحجام نصوص ومعاملات مقياس شاشة تمثيلية. قارن الإحداثيات والمسافات؛ ولا تعتمد المحاذاة بمجرد لقطة شاشة عابرة.

تفاوت البكسل المرسوم قاعدة تحقق، وليس إذنًا بترقيع الشيفرة بإزاحات نقطية خام. ينبغي أن تُحسَم العلاقات المتساوية من فئة المسافات نفسها أو دالتها أو الرمز نفسه أو تعريف الشبكة نفسه أو الهامش الداخلي المشترك للمكوّن. أصلح المالك المشترك عندما تختلف. وراعِ التخطيط الكسري وتقريب الجهاز (الهواتف بمعامل 2× و3×، ومقاييس سطح المكتب الكسرية) كي تقع المحاور المقصودة على البكسل الفعلي نفسه بدلًا من أن تنزاح عند مقاييس معينة.

  • حاذِ النص بخطوط الأساس، لا بمراكز المربعات المحيطة، عندما تشترك أحجام مختلفة في صف واحد.
  • وسّط الأيقونات في خانة ثابتة كي لا تتحرك التسميات عندما تختلف الأيقونات في عرضها الجوهري.
  • حاذِ الأرقام المتقاربة إلى النهاية واستخدم الأرقام الجدولية (tabular-nums)؛ وحاذِ النثر والمعرّفات إلى البداية ما لم تقتضِ اللغة المحلية خلاف ذلك.
  • أبقِ إجراءات الصفوف الطرفية ومؤشرات الإفصاح في مسارات ثابتة العرض.
  • حاذِ عناصر تحكم النماذج بإطارها التفاعلي، لا بنص المساعدة أسفلها.
  • استخدم justify-between (.justify_between()) فقط عندما يملك الجانبان فعلًا حافتين متقابلتين؛ ولا ينبغي أن يُخفي ذلك غياب بنية في المنتصف.
  • الخطوط الدقيقة تنتمي إلى مالك الحد. يجب ألا ترسم منطقتان متجاورتان الفاصل نفسه كلٌّ منهما.
  • يتبع شريط التمرير المنطقة التي تتمرر ويستقر إلى جانب حافة النهاية للوحة أو المحرر أو النافذة. يمكن لحشوة المحتوى أن تزيح النص والصفوف؛ لكن يجب ألا تسحب شريط التمرير إلى منتصف السطح. أضِف الحشوة إلى محتوى عرض التمرير، لا إلى عرض التمرير نفسه.

#مستويات الكثافة

المستوى المتوسط هو الافتراضي. غيّر الكثافة للسياق المحلي كله، لا لعنصر تحكم منفرد:

  • مريح / large: شاشات الاستقبال (onboarding)، والنماذج المتباعدة، والقرارات البارزة؛
  • قياسي / medium: معظم واجهة التطبيق وسير العمل، وكل شاشات اللمس؛
  • مدمج / small: أشرطة أدوات سطح المكتب، والقوائم، والجداول، والبيانات المهنية المتكررة؛
  • مدمج جدًا / xsmall: أدوات استثنائية عالية الكثافة، ولا يُختار تلقائيًا لتطبيق كامل أبدًا.

استخدم مقياسًا محدودًا بدلًا من أحجام اعتباطية. على شاشات اللمس يجب ألا يقل أي هدف قابل للضغط عن 44 × 44 نقطة (Touch targets). ومع المؤشر، تتدرج الأزرار عادةً عبر أطر تقارب 24 و28 و32 pt، وتمتد حقول الإدخال إلى نحو 40 pt في الحجم الكبير، وتستخدم صفوف الجداول نحو 28 و32 و40 pt. امنح مكوّنات التطبيق حجمًا يغيّر الخط والأيقونة والحشوة وهدف اللمس معًا؛ أما الارتفاع المخصص الذي يغيّر الصندوق الخارجي فقط فهو في الغالب ناقص.

#حجم النص والخط الأساسي والنقاط

يحافظ المقياس المصمَّم جيدًا على التراتب أثناء تكبير النص. ينجح تكبير النص عندما تظل العلاقة بين العنوان والمتن، وعنصر التحكم والأيقونة، والمسافة الداخلية والخارجية، والمناطق الأساسية والثانوية كما هي عند كل حجم، وليس فقط عندما يكبر كل شيء.

أطوال NSH بالنقاط، وحجم نص المستخدم (Dynamic Type على iOS، وحجم الخط على Android، وتحجيم النص في أنظمة سطح المكتب، أو إعداد التطبيق المضيف نفسه) يغيّر حجم النص: يكبر كل نص وإدخال يسمح بتحجيم الخط، بينما تحتفظ المسافات وأنصاف الأقطار والحدود والصور بحجمها (Text size). وحجم النص الأساسي للتطبيق (text-base في Tailwind، أي 16 نقطة) هو حجم المتن الذي يدور حوله مقياس الطباعة.

صمّم بالنِّسَب:

  • تحافظ درجات الخط على التراتب نفسه حول حجم المتن الأساسي؛
  • تحافظ درجات المسافات على علاقات التجميع نفسها حول النص؛
  • تكبر أطر عناصر التحكم وأهداف اللمس مع تسمياتها: حدّد أحجامها بمحتواها وبحد أدنى (min-h-11)، لا بارتفاع ثابت؛
  • تراعي الحدود الدنيا للألواح والعروض المريحة تكبير النص؛
  • تظل أنصاف أقطار الزوايا ومعالجة التركيز متسقة بصريًا مع إطار عنصر التحكم.

ولأن النص يكبر من تلقاء نفسه، فإن الإطار الثابت حوله ينكسر: تسمية أكبر داخل زر ثابت الارتفاع، أو مستند أكبر داخل حدود دنيا ثابتة للألواح، أو صفوف أكبر داخل ارتفاع صف ثابت، كل ذلك يهدم الإيقاع الأصلي وقد يقتطع المحتوى. دع الصفوف تلتف أو تتحول إلى عمود عندما يكبر النص (fontScale()، font_scale())، وضع سقفًا لمقياس النص الكبير أصلًا، مثل عنوان بحجم 34pt (maxFontSizeMultiplier).

كقاعدة عامة، ينبغي ألا يكتب تخطيط التطبيق قيم نقاط حرفية (p-[13px]، px(13.0)). استخدم مقياس المسافات (p-2، gap-3، w-64، text-sm ودوالها ذات الأنواع) أو أحجام مكوّنات التطبيق. لا تستخدم النقاط الحرفية إلا عندما تمثل القيمة حدًّا فيزيائيًا أو نقطيًا (raster): خطًّا دقيقًا، أو هامشًا داخليًا للنافذة تفرضه المنصة، أو أبعاد صورة نقطية، أو حدّ تفاوت أدنى لاختبار النقر، أو هندسة يجب أن تطابق سطحًا خارجيًا. هذه استثناءات مدقَّقة وموثَّقة. أما مسافات المنتج والطباعة وحجم الأيقونات وهندسة عناصر التحكم العادية فتبقى على المقياس.

اختبر عند عدة أحجام للنص، لا عند الحجم الافتراضي فقط. تحقق من التراتب والالتفاف والاقتطاع والحد الأدنى لحجم النافذة وتغيير حجم الألواح وفسحة حلقة التركيز وموضع النوافذ المنبثقة وقياس صفوف القوائم.

#الأسطح والارتفاع

استخدم الارتفاع لتوضيح ترتيب التراكب، لا لإبراز الأهمية. السطح الأساسي مسطّح، وتحدد الفواصل وتباين الخلفية مناطقه. يمكن أن تستخدم النوافذ المنبثقة (Popovers) والقوائم والأوراق (Sheets) ومربعات الحوار والإشعارات ظلالًا أقوى تدريجيًا لأنها تعلو المحتوى الآخر. لا تضع ظلًا على كل بطاقة.

يجب أن تشترك جميع الأسطح من النوع نفسه في معالجة واحدة. امنح عائلات العناصر المنبثقة سطحًا واحدًا ذا سمة (رموز popover والحدّ وradius وظل واحد) حتى لا تتباعد Popover وMenu وContextMenu وTooltip في مظهرها. وعندما يبتكر التطبيق سطحًا مثبّتًا آخر، أعد استخدام هذه المعالجة بدلًا من محاكاتها بقيم حدود وظلال حرفية لا صلة بينها.

#الخطوط والأيقونات

استخدم خط واجهة المستخدم الخاص بالمنصة لنصوص الواجهة، ولا تستخدم الخط أحادي المسافة إلا للشيفرة والمعرّفات والاختصارات والبيانات الرقمية المحاذاة. حافظ على قابلية قراءة النص الأساسي، وتجنب الإفراط في الأحرف الكبيرة أو تباعد الأحرف، خصوصًا في نصوص CJK.

استخدم عائلة أيقونات واحدة في المنتج. تكمّل الأيقونات التسميات النصية، ولا ينبغي أن تحل محلها في إجراءات غير مألوفة فيضطر المستخدم إلى التخمين. تحتاج الأزرار المقتصرة على الأيقونات دائمًا إلى اسم قابل للوصول (accessibilityLabel). أما شرحها البصري فيتبع سياسة التلميحات (tooltip) الواردة أدناه. استخدم الأيقونات الممتلئة أو الملونة للتعبير عن حالة، لا لمجرد جعل شريط الأدوات أكثر حيوية.

#أنماط التخطيط

#اختيار هيكل ثابت

ينبغي لمعظم التطبيقات أن تستخدم أحد هذه الهياكل:

  • مساحة عمل واحدة: ترويسة أو شريط أدوات فوق عرض أساسي واحد؛
  • مكدس التنقل: صفحات تُدفع وتُسحب (NavStack)، مع إيماءة الرجوع الخاصة بالنظام على الهواتف؛
  • مساحة عمل بشريط جانبي: تنقل دائم بجانب عرض تفصيلي متغير؛
  • القائمة والتفاصيل (master–detail): لوحتان قابلتان لتغيير الحجم للمجموعة والتفاصيل، وصفحتان على الهاتف؛
  • مساحة عمل المستندات: علامات تبويب لعدة كائنات طويلة العمر؛
  • نافذة أداة مساعدة: مهمة واحدة مركّزة بمسار إجراءات قصير وثابت.

حافظ على ثبات التنقل العام بينما يتغير المحتوى. امنح منطقة العمل الأساسية المساحة المتبقية باستخدام flex-1 (.flex_1()) وmin-w-0 / min-h-0 حيث يجب أن تنكمش العناصر الفرعية التي تفيض. استخدم Scroll وList للسلوك المقصود منهما بدلًا من إعادة بناء التمرير أو المحاكاة الافتراضية (virtualization) من عروض متداخلة.

على أنظمة سطح المكتب، يُعدّ شريط العنوان الخاص بالتطبيق (nsh/desktop) إطار نافذة في المقام الأول. حافظ على منطقة السحب فيه (.window_drag(true)) وتجنب ربط النقرات العادية على العنوان بأوامر تحرير قليلة الاستخدام. ينبغي أن يكون تغيير الاسم خلف أمر صريح على الكائن، مع مسار لوحة مفاتيح عند الاقتضاء.

#النوافذ المتجاوبة

قد يكون حجم النافذة أي حجم: يحدده المستخدم أو العرض المنقسم أو التطبيق المضيف. حدّد ما يحدث عند تضيّق النافذة:

  1. حافظ على المهمة الأساسية؛
  2. اسمح للمناطق القابلة لتغيير الحجم بالوصول إلى حدّ أدنى موثّق؛
  3. اطوِ التسميات الثانوية أو لوحات الفحص (inspectors)؛
  4. انقل الإجراءات قليلة التكرار إلى قائمة؛
  5. مرّر المنطقة التي يفيض محتواها فعلًا فقط.

لا تُخفِ إجراءً دون توفير مسار آخر للوصول إليه. تجنب جعل النافذة بأكملها قابلة للتمرير عندما ينبغي أن تتمرر قائمة أو متن مستند فقط. تتبع نقاط التوقف (md:، lg:) وwindowSize() (window() في Rust) السطح الذي يرسم NSH عليه.

يواجه التخطيط المرن (flex) في NSH ضغط الحجم الجوهري نفسه الموجود في أنظمة التخطيط الأخرى: فقد يرفض عنصر فرعي flex-1 الانكماش حول محتوى طويل. يجب أن يتفق التصميم والتنفيذ على اللوحات التي يجوز لها أن تنكمش أو تُقتطع أو تلتف أو تتمرر. كما أن المنطقة المقتطعة تقتطع حلقة التركيز الخارجية؛ فلا تضحِّ أبدًا برؤية التركيز عبر لوحة المفاتيح لمجرد تبسيط معالجة الفيضان.

#النماذج والإعدادات

استخدم تسمية مرئية لكل حقل، وضع التعليمات أو رسائل التحقق بجوار الحقل الذي تصفه. حاذِ الحقول المترابطة، لكن لا تفرض تسميات طويلة داخل عمود ثابت ضيق. استخدم عنصر التحكم المناسب: Checkbox للخيارات المستقلة، وRadioGroup من Radio لمجموعة صغيرة مرئية، وMenu من الخيارات لمجموعة أطول، وSwitch للإعداد الذي يسري فورًا. أجزاؤها (مسار المفتاح ومقبضه، ومؤشر خانة الاختيار) من شأن التطبيق في تنسيقها، عبر الأصناف checked: وindeterminate: وdisabled: التي تتبع عنصر التحكم:

use nsh_base::{switch, theme};

fn setting_switch(label: &'static str, on: Signal<bool>) -> impl IntoElement {
    switch()
        .checked(on)
        .on_change(move |next: &bool| on.set(*next))
        .flex_row()
        .items_center()
        .gap_3()
        .track_style(|s| {
            s.w(px(44.0))
                .p(px(2.0))
                .rounded_full()
                .bg(move || theme().muted)
                .transition_colors()
                .checked(|s| s.bg(move || theme().primary))
        })
        .thumb_style(|s| {
            s.size(px(20.0))
                .rounded_full()
                .bg(move || theme().background)
                .shadow_sm()
                .transition_transform()
                .checked(|s| s.translate_x(px(20.0)))
        })
        .child(text(label).text_color(move || theme().foreground))
}

تُظهر خانة اختيار «تحديد الكل» التحديد الجزئي عبر حالتها غير المحددة (indeterminate):

use nsh_base::{checkbox, theme};

fn select_all(selected: Signal<usize>, total: usize) -> impl IntoElement {
    checkbox()
        .checked(move || selected.get() == total)
        .indeterminate(move || selected.get() > 0 && selected.get() < total)
        .on_change(move |all: &bool| selected.set(if *all { total } else { 0 }))
        .flex_row()
        .items_center()
        .gap_2()
        .indicator_style(|s| {
            s.size(px(20.0))
                .items_center()
                .justify_center()
                .rounded(move || theme().radius)
                .border_2()
                .border_color(move || theme().border)
                .checked(|s| s.bg(move || theme().primary).border_color(move || theme().primary))
                .indeterminate(|s| s.bg(move || theme().primary).border_color(move || theme().primary))
        })
        .indicator(text(move || mark(selected.get(), total)).text_color(move || theme().primary_foreground))
        .child(text("Select all").text_color(move || theme().foreground))
}

fn mark(selected: usize, total: usize) -> &'static str {
    if selected == total { "✓" } else if selected > 0 { "–" } else { "" }
}

عطّل الإرسال أثناء تنفيذ العملية، واحتفظ بما أدخله المستخدم، وأظهر النتيجة قرب الإجراء. أبقِ حقول الإدخال فوق لوحة المفاتيح البرمجية (env(keyboard-inset-height)). خصّص مربعات الحوار للقرارات القصيرة المركّزة؛ واستخدم صفحة كاملة أو ورقة (sheet) لسير العمل الذي يتطلب استكشافًا أو حقولًا كثيرة.

يجب أن تمثّل معالجة التحميل انتظارًا حقيقيًا. ينبغي أن يكون التبديل بين لوحات إعدادات محتواها متاح أصلًا فوريًا؛ ولا يناسب الهيكل العظمي (skeleton) إلا أثناء تحميل المحتوى فعليًا حين يكون شكله معروفًا.

#المكونات والتركيب

اتبع مبدأ Shadcn القائل إن المكونات مواد بناء وليست نظام تصميم مغلقًا. يوفر nsh/base السلوك؛ ويوفر التطبيق القيم الافتراضية المتسقة في مكوناته الخاصة، ويملك التركيب ودلالات المنتج.

  • استخدم متغيرات المكونات بحسب المعنى. يُحجز primary للإجراء الافتراضي الصريح الذي يُنهي القرار في منطقة اتخاذ القرار، وهو عادةً الإجراء الذي يستدعيه Return على أنظمة سطح المكتب. ولا يصبح الإجراء المنفرد أو المتكرر أو المرغوب فيه primary تلقائيًا. يستخدم أمر Add في شريط أدوات الإدارة عادةً زرًا افتراضيًا (default)؛ أما الإجراء الافتراضي Create في النموذج فقد يستخدم primary. استخدم destructive للالتزام بإجراء هدّام، وghost لإجراءات شريط الأدوات الهادئة.
  • فضّل الأجزاء والفتحات (slots) الصريحة على تنسيق عناصر فرعية عشوائية.
  • حافظ على اتساق النمط المتكرر عبر المنتج. غلّفه في مكوّن تطبيق عندما يحمل لغة المجال أو سياسته.
  • استخدم المكوّن القياسي لدوره الدلالي. Menu وContextMenu وPopover وSheet وDialog وAlertDialog ليست صناديق قابلة للتبادل؛ فلكلٍّ منها عقود مختلفة للتحديد والتركيز ولوحة المفاتيح والإغلاق والتخطيط، ويتخذ شكله الخاص على كل جهاز.
  • حافظ على هندسة عائلة المكوّن. تتشارك صفوف القائمة في الحشوة الرأسية والأفقية والارتفاع وفتحات الأيقونة وعلامة الاختيار والفواصل ونصف القطر ومعالجة الحالات. لا تحاكِ قائمة بنافذة منبثقة مخصصة لا يقارب تباعدها تباعد النظام إلا تقريبًا.
  • لا تغلّف مكوّنًا لمجرد إعادة تسمية كل خاصية (prop) أو تجميد كل إمكاناته.
  • انقل السلوك القابل لإعادة الاستخدام الخالي من تنسيق المنتج إلى nsh/base، أو إلى crate تتشاركه منتجات التطبيق؛ وأبقِ العرض المقولَب ذا الطابع الخاص في مكونات التطبيق.
use nsh_base::{button, theme};

/// The footer of a form: a default Cancel and the primary commit.
fn form_actions(cancel: impl Fn() + 'static, create: impl Fn() + 'static) -> impl IntoElement {
    view()
        .flex_row()
        .justify_end()
        .gap_2()
        .child(
            button()
                .on_press(cancel)
                .rounded(move || theme().radius)
                .border_1()
                .border_color(move || theme().border)
                .px_4()
                .py_2()
                .active(|s| s.bg(move || theme().muted))
                .child(text("Cancel").text_color(move || theme().foreground)),
        )
        .child(
            button()
                .on_press(create)
                .rounded(move || theme().radius)
                .bg(move || theme().primary)
                .px_4()
                .py_2()
                .active(|s| s.bg(move || theme().primary.opacity(0.9)))
                .child(text("Create").text_color(move || theme().primary_foreground)),
        )
}

#حالات التفاعل

#اجعل النتيجة مفهومة قبل النقر

ينبغي أن يتنبأ عنصر التحكم بنتيجته. استخدم عناصر تحكم ومواضع مألوفة في المنصة ليتمكن الناس من التصرف دون تعلّم الواجهة أولًا. تسمّي تسميته الإجراء والكائن، وتبيّن حالته مدى التوفر، ويؤكد التعليق عليه النتيجة نفسها.

لا تسمِّ زرًا Button باسم Save إذا كان يفتح مسار إعداد، ولا باسم Delete إذا كان يزيل عنصرًا من مجموعة فقط. اذكر النطاق عندما لا يوضحه السياق. استجب فورًا للتفعيل، وامنع الإرسال المكرر أثناء الأعمال الأطول، وأظهر النتيجة قرب الكائن الذي تغيّر. أضف رسالة نجاح فقط عندما لا تكون النتيجة نفسها مرئية.

ينبغي تصميم كل عنصر تحكم تفاعلي للحالات التالية:

الحالةNSHمتطلب التصميم
السكونإيحاء واضح بإمكانية التفاعل دون ضجيج بصري
التمرير (Hover)hover:تغذية راجعة خفيفة للمؤشر، ولا تكون الإشارة الوحيدة أبدًا
الضغطactive:تغذية راجعة فورية للضغط، ينسّقها التطبيق
مفتوح / مضغوطopen الخاص بالنافذة المنبثقةتغذية راجعة مستمرة طالما كانت نافذة منبثقة مرتبطة مفتوحة
التركيز المرئيfocus-visible:حلقة تركيز عالية التباين للوحة المفاتيح
محدد / مفعّلselected:، checked:حالة دائمة تتميز عن التمرير
معطّلdisabled:تأكيد أقل ولا استجابة مضللة للتمرير أو الضغط
التحميلحافظ على السياق، وامنع تكرار الإجراء، واشرح الانتظارات الطويلة
الخطأاذكر ما حدث وكيفية التعافي

اربط الأوامر التي ينبغي أن تعمل من لوحة المفاتيح باستخدام keymap على أنظمة سطح المكتب والويب. طابق اختصارات المنصة المألوفة ("mod+k" هو Command على macOS وControl في غيره)، وأظهر الاختصارات في القوائم أو التلميحات، وأبقِ التركيز في مكان منطقي بعد فتح أي طبقة علوية أو إغلاقها.

التحديد جزء من نموذج المعلومات، وليس تحسينًا شكليًا اختياريًا. يجب أن تُظهر علامات التبويب والخيارات المجزأة والصفوف القابلة للتحديد والمرشحات ووجهات التنقل حالة تحديد دائمة (selected، ويعلنها قارئات الشاشة). ويجب أن يبقى الزر الذي يملك قائمة مضغوطًا أو مفتوحًا بصريًا حتى تُغلق النافذة المنبثقة؛ فالتمرير وحده لا يشرح العلاقة بين المشغّل والسطح.

use nsh_base::{tab, theme};

fn section_tab(label: &'static str, id: Section, current: Signal<Section>) -> impl IntoElement {
    let selected = move || current.get() == id;
    tab()
        .selected(selected)
        .on_press(move || current.set(id))
        .rounded(move || theme().radius)
        .px_3()
        .py_2()
        .selected_style(|s| s.bg(move || theme().muted))
        .child(text(label).text_color(move || {
            if selected() { theme().foreground } else { theme().muted_foreground }
        }))
}

في عنصر التحكم المجزأ (segmented control)، يجب أن يتشارك تعبئة العنصر المحدد والسطح غير المحدد في الشكل الخارجي للحاوية نفسها. ولا يجوز لأيٍّ منهما أن يجعل زاوية طرفية حادة أو يجعل حدًّا يبدو أثقل من نظرائه.

أظهر عنصر التنقل أو صف القائمة أو علامة التبويب المحددة عبر سطح العنصر نفسه: تعبئة محددة، أو لون أمامي أقوى، أو وزن خط أثقل. لا تضف شريطًا على الحافة الأمامية أو حدًّا أحادي الجانب كعلامة تحديد. فهذه عادة من قوالب الويب وليست عُرفًا أصيلًا في الواجهات الأصلية؛ فهي تكسر الشكل المستدير للعنصر وتضيف حافة ثانية منافسة إلى عمود يتحاذى أصلًا على نصه.

بالنسبة للإجراءات الهدّامة، ميّز بين العمل القابل للتراجع وغير القابل له. فضّل التراجع (undo) أو إشعارًا مؤقتًا للتغييرات القابلة للتراجع. استخدم AlertDialog عندما تكون العاقبة خطيرة ولا يمكن التراجع عنها؛ وسمِّ الكائن المحدد والعاقبة في نص التأكيد.

#اصطلاحات المؤشر

استخدم المؤشر الافتراضي للأزرار وخانات الاختيار وعناصر القوائم وعلامات التبويب وعناصر التحكم الأخرى. استخدم اليد المشيرة (cursor-pointer) للروابط والمحتوى الذي يتصرف كرابط. استخدم مؤشرات النص وتغيير الحجم والإمساك والمنع فقط عندما تصف المعالجة الجارية. يعزز المؤشر الإيحاء بإمكانية التفاعل، لكنه لا يحل محل الحالة المرئية لعنصر التحكم ولا دوره القابل للوصول.

اجعل تأثيرات التمرير معتدلة لأن اللمس ولوحة المفاتيح وإمكانية الوصول لا تتضمن تمريرًا. لا تُظهر عند التمرير النسخة الوحيدة من إجراء هدّام أو أساسي. يجوز أن تصبح إجراءات الصف السياقية أهدأ في حالة السكون إذا بقيت الأوامر نفسها متاحة عبر التحديد أو لوحة المفاتيح أو قائمة سياقية.

#فضّل أسطح الأوامر على أشرطة الأدوات عند التمرير

استخدم تكرار الأمر ونطاقه لاختيار مكان وجود الإجراء:

  • أبقِ الإجراء الأساسي أو المتكرر مرئيًا كزر ذي تسمية أو عنصر تحكم مألوف في شريط الأدوات؛
  • ضع الإجراءات الثانوية للمنطقة الحالية خلف مشغّل Menu مرئي؛
  • ضع الأوامر التي تعمل على الكائن تحت المؤشر أو الإصبع في ContextMenu: نقرة يمنى على أنظمة سطح المكتب، وضغطة إصبع مطوّلة على شاشات اللمس؛
  • أتح الأمر المهم نفسه عبر اختصار لوحة مفاتيح (keymap) عندما يكون له شكل طبيعي في لوحة المفاتيح، وفي قائمة التطبيق (Menu.setApplicationMenu من nsh/desktop)؛
  • لا تستخدم أيقونة تظهر عند التمرير إلا كاختصار لأمر يبقى متاحًا في مكان آخر.

هذا أكثر من مجرد تفضيل بصري. فقوائم nsh/base تتولى أصلًا التموضع عند المشغّل، والقوائم الفرعية المتتالية على أنظمة سطح المكتب، وورقة بقوائم فرعية في مكانها على الهواتف، والإغلاق باختيار عنصر أو بالضغط خارجها أو Escape أو الرجوع، وأدوار القائمة وعنصر القائمة لقارئات الشاشة. أما شريط مخصص من أزرار التمرير فيتعين عليه إعادة بناء هذه السلوكيات، وهو غير مرئي لسيناريوهات اللمس ولوحة المفاتيح فقط والعديد من التقنيات المساعدة.

use nsh_base::{menu, menu_item, menu_separator, theme};
use nsh_core::{color::black, element::StyleRefinement};

/// The app's popup surface, shared by its menus, popovers and tooltips.
fn popup(s: StyleRefinement) -> StyleRefinement {
    s.min_w(px(192.0))
        .p_1()
        .rounded(move || theme().radius)
        .border_1()
        .border_color(move || theme().border)
        .bg(move || theme().popover)
        .shadow_lg()
}

fn item(label: &'static str, on_select: impl Fn() + 'static) -> impl IntoElement {
    menu_item()
        .on_select(on_select)
        .px_3()
        .py_2()
        .rounded(move || theme().radius)
        .hover(|s| s.bg(move || theme().muted))
        .active(|s| s.bg(move || theme().muted))
        .child(text(label).text_color(move || theme().foreground))
}

fn more_menu(rename: impl Fn() + 'static, duplicate: impl Fn() + 'static, delete: impl Fn() + 'static) -> impl IntoElement {
    menu()
        .offset(px(4.0))
        .popup_style(popup)
        .backdrop_style(|s| s.bg(black().opacity(0.5)))
        .trigger(
            view()
                .label("More actions")
                .size(px(44.0))
                .items_center()
                .justify_center()
                .rounded_full()
                .active(|s| s.bg(move || theme().muted))
                .child(text("⋯").text_color(move || theme().foreground)),
        )
        .child(item("Rename…", rename))
        .child(item("Duplicate", duplicate))
        .child(menu_separator().h(px(1.0)).my_1().bg(move || theme().border))
        .child(item("Delete…", delete))
}

اختر Menu عندما يحتاج المستخدمون إلى إشارة مرئية بوجود أوامر إضافية، مثل قائمة فائض شريط الأدوات أو إجراءات المستند أو قائمة الحساب. واختر ContextMenu للأوامر المرتبطة بالتحديد أو بالكائن مثل إعادة التسمية والتكرار والإظهار والإزالة. يجب ألا تكون القائمة السياقية الطريق الوحيد لتنفيذ أمر أساسي؛ وفّر مسارًا عبر شريط القوائم أو شريط الأدوات أو لوحة المفاتيح أو عرض التفاصيل بحسب الحاجة.

لا تضع كل إجراء في قائمة لمجرد جعل الشاشة تبدو بسيطة. فسهولة الاكتشاف والسرعة مهمتان: يبقى الإجراء الرئيسي مرئيًا، وتبقى العناصر الخطرة معنونة بوضوح ومفصولة، وينبغي أن يستخدم عنصر القائمة الفعل والأيقونة والاختصار وحالة التمكين والنتيجة نفسها أينما ظهر.

#الزر للإجراءات داخل التطبيق؛ والرابط للموارد الخارجية

استخدم Button عندما يغيّر التفعيل حالة التطبيق، أو يؤكد قرارًا، أو يفتح أداة، أو يرسل بيانات، أو ينفّذ أمرًا. اختر شكله بحسب التسلسل الهرمي المحلي:

  • الزر الأساسي (primary) للالتزام الوحيد المؤكَّد في منطقة القرار؛
  • الزر الافتراضي (default) للإجراءات المرئية العادية؛
  • الزر ذو الحدود (outline) عندما يحتاج الإجراء إلى حدود واضحة بتأكيد أقل؛
  • الزر الشفاف (ghost) للإجراءات المألوفة منخفضة التأكيد في أشرطة الأدوات وضمن النص؛
  • زر الأيقونة فقط للرمز المعروف جيدًا، مع اسم يمكن الوصول إليه، وتلميح أداة عندما يكون المعنى أو النطاق غير واضح.

لا تجعل الزر أساسيًا لأنه الإجراء الوحيد على الشاشة، أو لأنه موضوع في أعلى اليمين، أو لأن الفريق يريد نقرات أكثر. الزر الأساسي يعبّر عن الالتزام الافتراضي وسلوك لوحة المفاتيح. وإذا كان التفعيل مجرد أمر عادي مثل إضافة عنصر أو فتح أداة أو تحديث عرض، فاستخدم زرًا افتراضيًا أو ذا حدود أو شفافًا بحسب موقعه في التسلسل الهرمي المحلي.

استخدم الرابط المسطَّر (role="link") فقط لهدف خارجي: عنوان URL، أو صفحة ويب، أو توثيق عبر الإنترنت، أو عنوان بريد إلكتروني. يستخدم الرابط مؤشر اليد لأن عقده هو مغادرة سياق التطبيق الحالي إلى ذلك المورد. لا تستخدم تنسيق الرابط لجعل أمر وظيفي يبدو هادئًا. فإجراء بشكل رابط مثل حذف أو حفظ أو تحديث أو إضافة أو فتح القائمة أو التنقل داخل التطبيق يُخفي قابلية استخدام العنصر ويكشف دور إمكانية وصول خاطئًا.

كلمة «عرض» لا تجعل الوجهة داخل التطبيق رابطًا. فالتقرير الكامل أو التحليل أو لوحة التفاصيل أو السجل المحلي يُفتح عبر زر أو صف أو بطاقة أو علامة تبويب أو عنصر كشف. استخدم تسميات موجزة تراعي السياق، مثل Full analysis، عندما تحدد البطاقة الحاوية ما سيُفتح؛ واحتفظ بالتسطير للمورد الذي يُفتح فعلًا في المتصفح أو عميل البريد.

يجب أن يستخدم كل تنقل داخلي، مثل صفوف الشريط الجانبي وعلامات التبويب ومسارات التنقل وعناصر القوائم وفتح عرض محلي والتبديل بين مساحات العمل، حزمة التنقل (navigation stack)، أو Button، أو المكوّن المناسب. يُختار التأكيد البصري عبر نوع الزر (variant) أو حالة التحديد في مكوّن التنقل، ولا يُختار أبدًا بالتلاعب بالدلالات.

#الملاحظات والطبقات المنبثقة

اختر أصغر سطح يناسب القرار:

  • تلميح الأداة: شرح قصير أو اختصار (Tooltip، أو tooltip لأي عنصر)؛
  • النافذة المنبثقة: عناصر تحكم سياقية لا تقاطع المهمة (Popover)؛
  • القائمة: قائمة مدمجة من الإجراءات (Menu)؛
  • الإشعار: حالة غير متزامنة لا تتطلب قرارًا؛
  • مربع الحوار: قرار مركّز أو نموذج قصير (Dialog)؛
  • مربع حوار التنبيه: تأكيد صريح لإجراء ذي عواقب (AlertDialog)؛
  • الورقة (sheet): عمل مكمّل يستفيد من مساحة أكثر ثباتًا (Sheet).

يتخذ كل منها الشكل الذي يتوقعه الجهاز، من بنية واحدة يضبط التطبيق مظهرها (popupClass، backdropClass):

أجهزة سطح المكتبالهواتف والأجهزة اللوحية
Tooltipبعد أن يستقر المؤشر على العنصرفقط عندما يفتحه التطبيق
Popoverمثبّت بالعنصر؛ الضغط خارجه يغلقه ويستمرمثبّت بالعنصر؛ الضغط خارجه يغلقه فقط
Menuنافذة منبثقة عند العنصر المشغِّل، مع قوائم فرعية متتاليةورقة في الأسفل؛ تحل القائمة الفرعية محل عناصرها
ContextMenuزر الفأرة الثانوي، عند المؤشرالضغط المطوّل بالإصبع يفتح الورقة
Sheetلوحة جانبية فوق خلفية معتمةالشيء نفسه
AlertDialogتنبيه بنمط مربعات الحوار في المنصةتنبيه النظام أو ورقة الإجراءات

يضيف تلميح الأداة معنى ناقصًا، ولا يكرر إجراءً واضحًا. احتفظ به لرمز غير مألوف، أو نطاق ملتبس، أو اختصار مفيد يكون مخفيًا لولا التلميح. أما الأوامر المتكررة المعروفة مثل نسخ وتنزيل في تذييل الرسالة فتحتاج إلى أسماء يمكن الوصول إليها ووصول عبر لوحة المفاتيح، لكن تسمياتها عند التمرير تضيف مقاطعة بدل الوضوح. تعرض الهواتف تلميح الأداة فقط عندما يفتحه التطبيق، لذلك يجب ألا يحمل التلميح أبدًا ما يحتاجه مستخدم اللمس.

يقاطع التنبيه التسلسل البصري الهرمي حتى عندما لا يفتح نافذة مشروطة (modal). استخدمه لمعلومات مهمة واستثنائية تحتاج إلى انتباه في المهمة الحالية، لا كحاوية مزخرفة لأوصاف عادية أو نصائح أو لملء مساحة فارغة. فضّل المساعدة المضمّنة أو النص الخافت أو قسمًا عاديًا عندما لا يتطلب المحتوى انتباهًا فوريًا أو إجراءً.

تجنّب تكديس الطبقات المنبثقة. يغلق مفتاح Escape وزر الرجوع في Android أعلى طبقة قابلة للإغلاق؛ أبقِ حالة التطبيق متزامنة من onOpenChange، وأعد التركيز إلى العنصر المشغِّل أو إلى الهدف المنطقي التالي.

يجب أن يشير إجراء الطبقة المنبثقة إلى كائن أو حالة تعرضها الطبقة فعلًا. فعلى سبيل المثال، اعرض Clear history فقط عندما يكون قسم سجل حديث مستقل ظاهرًا ويحتوي على عناصر. نتائج البحث والعناصر الأخيرة والمفضلة مجموعات مختلفة؛ سمِّها وافصل بينها بدل دمجها في قائمة واحدة غير مفسَّرة. أخفِ الإجراء غير المنطبق أو عطّله مع سبب مفيد، ولا تضع أيقونة سلة مهملات ملتبسة في التذييل.

مساحة التذييل ليست مكانًا لكل قدرة لم تجد موضعًا في التصميم. قد يعرض التذييل اختصارات أو حالة أو إجراءات تنطبق على السطح بأكمله، لكن على كل عنصر فيه أن يجيب: ما كائنه، ولماذا هو متاح الآن، وما النطاق الذي يؤثر فيه، وما الحالة المرئية التي تتغير بعد التفعيل؟

#الحركة

الحركة تشرح التغيير؛ وليست زخرفة محيطية. استخدم انتقالات قصيرة للظهور والإغلاق والتوسيع والاستمرارية المكانية. تجنّب تحريك تغييرات التخطيط الكبيرة عندما يعبّر التعتيم (opacity) أو التحويل (transform) عن العلاقة نفسها. احترم تفضيلات تقليل الحركة (motion-reduce:، reduceMotion())، ولا تشترط الحركة أبدًا لفهم الحالة، ولا تضف حركة افتراضية إلى كل مكوّن.

view()
    .on_press(open)
    .transition_transform()
    .duration(150.0)
    .when(!reduce_motion(), |v| v.active(|s| s.scale(0.97)))

سياسة الحركة تنتمي إلى طبقة التطبيق. يملك nsh/base آلية دورة الحياة أو الهندسة اللازمة للانتقال، لكنه لا يقرر أن كل منتج يتلاشى أو ينزلق. امنح القيم المتحركة باستقلال هوية ثابتة، واجعل المقاطعة تنعكس بسلاسة انطلاقًا من القيمة المأخوذة حاليًا بدل إعادة البدء من نقطة نهاية قديمة.

تتبع الحركة استمرارية التفاعل. أثناء تحرك المؤشر أو موضع التمرير بين أهداف مترابطة، أبقِ سطح المعاينة ثابتًا وحدّث محتواه أو موضعه في مكانه. لا تُغلقه إلا بعد انتهاء التفاعل؛ فتكرار التلاشي ثم الظهور عند كل تحديث يبدو كوميضٍ مزعج.

#تصميم الواجهات كثيفة البيانات

الكثافة لا تعني الازدحام. في الجداول والأشجار والقوائم الطويلة والمحررات:

  • أبقِ الترويسات وهوية الصف الأساسية ثابتة بصريًا؛
  • حاذِ القيم القابلة للمقارنة واستخدم الأرقام الجدولية (tabular numerals) حيث يلزم؛
  • ميّز بين التركيز والتمرير والصف النشط والتحديد المتعدد؛
  • أبقِ الفرز والتصفية ظاهرين وقابلين للتراجع؛
  • احفظ التحديد بحسب هوية المجال عبر التصفية وإعادة الترتيب؛
  • طبّق التمرير الافتراضي (virtualization) على المجموعات الكبيرة (List) دون تغيير دلالات لوحة المفاتيح؛
  • استخدم الكشف التدريجي للأعمدة الثانوية ولوحات الفحص؛
  • وفّر حالة فارغة مفيدة تشرح الإجراء التالي.

اختر الجدول للمقارنة عبر حقول متسقة، والقائمة لمسح عناصر غير متجانسة، والشجرة لتسلسل هرمي حقيقي. لا تستخدم تخطيط بيانات معقدًا كأنه أسلوب بصري.

#لغة الواجهة

الكلمات جزء من بنية الواجهة. اكتب مفردات الميزة كنظام متكامل، من وجهات وكائنات وأوامر وحالات ونتائج، لا كترجمات منعزلة لميزات التنفيذ. فضّل أقصر صياغة تبقى دقيقة في سياقها الفعلي.

#دع السياق يحمل السياق

لا تكرر معلومات يحددها السطح المحيط أصلًا. فوجهة الشريط الجانبي هي عادةً الكائن أو المجال نفسه: استخدم Users لا User Management؛ وShortcuts لا Shortcut Configuration Management. ويمكن لعمود تحتوي صفوفه على إجراءات أن يستغني عن ترويسة عامة مثل Operation. ومربع حوار بعنوان Delete “Roadmap”? لا يحتاج إلى نص في المتن يطرح السؤال نفسه مرة أخرى.

هذا اقتصاد في السياق، لا حذف لمجرد الحذف. أضف نصًا عندما يغيّر القرار: لتحديد النطاق المتأثر، أو عاقبة لا رجعة فيها، أو شرطًا مسبقًا غير معتاد، أو طريقة للاسترداد. على كل كلمة إضافية أن تجيب عن سؤال لا يجيب عنه التخطيط الحالي.

استخدم الأسماء للوجهات والكائنات (Users، Appearance، Orders)، والأفعال للأوامر (Save، Duplicate، Export)، والصفات أو العبارات القصيرة للحالات (Offline، Up to date، Pending review). تجنّب الكلمات الغلافية مثل Management وModule وPage وFunction وOperation وSystem ما لم تميّز الكلمة مفهومًا حقيقيًا في المجال.

#اكتب كل لغة بأسلوبها، ولا تترجم شكل الجملة

ابدأ من القصد المشترك والتسلسل الهرمي والمصطلحات، ثم صُغ كل لغة محلية كلغة واجهة طبيعية. لا تحافظ على ترتيب كلمات اللغة المصدر ولا على عدد كلماتها ولا على حشو المجاملة ولا على فئتها النحوية. فكلمة Users بالإنجليزية يمكن أن تعبّر عن مفهوم ميزة بالصينية كان سيتوسع حرفيًا إلى «إدارة المستخدمين»؛ فالأمانة تعني الحفاظ على الغرض، لا على الألفاظ.

احذف الكلمات التي توفرها بنية المعلومات المحيطة. ففي سطح Settings تكون الوجهة غالبًا ببساطة Account، لا Account Settings، ولا المفرد غير الطبيعي Account Setting أبدًا. يُختار التسمية الإنجليزية الصحيحة من دورها وجيرانها، لا من العبارة المصدر منفردة.

حافظ على معجم منتج صغير للكائنات والأوامر والحالات المتكررة. استخدم المصطلح نفسه في شريط الأدوات والقائمة وقائمة السياق ومربع الحوار والبحث عن الاختصارات والتوثيق، ما لم يغيّر السياق معناه فعلًا. راجع النص في السطح المعروض: فالتسميات المجاورة كثيرًا ما تكشف تكرارًا أو نطاقًا غير متسق لا يظهر في ملف اللغة.

في الكتابة التقنية المحلية، احتفظ بمصطلح إطار العمل المعتمد عندما تكون ترجمته أقل دقة. أبقِ معرّفات API بصيغتها الأصلية وهيّئها كشيفرة. ولا تُبقِ كلمات أجنبية عادية لمجرد أن تبدو تقنية. اشرح المصطلح المحتفظ به عند أول استخدام إن لزم، ثم استخدم الصيغة نفسها في الواجهة والتوثيق وأمثلة API.

في التوثيق، استخدم الاسم الإنجليزي الدقيق لأي API أو مكوّن أو ملف مصدر أو دليل مسمّى في نص الرابط، بما في ذلك الصفحات المترجمة. فاكتب View وButton وMenu وText بدل ترجمة أسمائها أو إضافة البادئة الزائدة NSH. أبقِ الشروحات المحيطة بلغة الصفحة. اربط الهدف المسمّى فقط؛ وضع كلمات مثل «المصدر» خارج الرابط عندما تصف سبب الاستشهاد به.

في جدول المقارنة التقني المشترك بين اللغات، أبقِ أسماء القدرات وأسماء أطر العمل وأسماء API وأسماء النماذج بصيغتها الإنجليزية المعتمدة. ترجم الشرح المحيط بالجدول، لا تسمياته التقنية. أما كلمات الواجهة الشائعة التي لها اسم محلي معتمد فيجوز توطينها خارج سياق المقارنة هذا.

#الأزرار وحوارات التأكيد

تكون تسميات الأزرار قصيرة افتراضيًا، وعادةً من كلمة أو كلمتين، وتصف النتيجة لا الإيماءة ولا المكوّن. فضّل Save أو Move أو Delete على Click to save أو Perform move أو Confirm deletion. استخدم Cancel باستمرار للإجراء الذي يخرج دون تنفيذ التغيير. واحتفظ بـ OK للإقرار بمحتوى معلوماتي بحت.

القِصر قاعدة افتراضية وليس حدًّا للأحرف. التسمية الأطول عمدًا أفضل عندما تكشف كلماتها عن عاقبة أو تميّز بين خيارات قد يخلط المستخدمون بينها، مثل Delete from this group مقابل Delete everywhere, or Restart without saving. يجب أن يقدّم الطول معلومة حاسمة للقرار، ويجب ألا يكرر عنوان الحوار أو نصه.

استخدم أدق نتيجة موجزة ممكنة تسميةً للتأكيد:

السياقضعيفالمفضّل
حوار الحذفYes، Sure، Confirm deletionDelete
تغييرات غير محفوظةConfirm، YesDiscard changes
إقرار بحتConfirm operationOK أو Done
موافقة معقدة لا يوجد لنتيجتها فعل واضحYesConfirm

Confirm بديل احتياطي مفيد عندما يسمّي الحوار المحيط الالتزام المعقد تسمية كاملة، ولا يوجد فعل نتيجة أقصر دقيق. ويجب ألا يحل محل أمر واضح. أما Sure فهو عبارة محادثة وليس أمرًا إنجليزيًا ثابتًا، وهو شديد الغموض بحيث لا يصلح للمفردات القياسية.

يجب أن يشكّل حوار التأكيد قرارًا واحدًا موجزًا:

  • العنوان: القرار أو الحالة، مثل Delete “Roadmap”?؛
  • النص: نطاق جديد أو عاقبة أو معلومات استرداد فقط؛
  • الإجراءات: Cancel والنتيجة، مثل Delete؛
  • التنسيق التحذيري: يُطبَّق على النتيجة المدمّرة (إجراء AlertDialog بدور destructive)، ولا يُستخدم بديلًا عن الصياغة الدقيقة.

تجنّب العناوين العامة مثل Notice وWarning وError وConfirmation عندما يمكن تسمية الحالة الفعلية. تجنّب العبارات الطقسية مثل “Are you sure you want to…” و”Would you like to…” و”Please note that…” و”successfully” عندما تعبّر البنية أو الحالة عنها أصلًا. ينبغي أن تأتي المجاملة من نبرة هادئة ومحترمة، لا من تكرار please.

#الحروف الكبيرة وعلامات الترقيم والرموز

استخدم نمط الجملة (sentence case) لواجهات المستخدم الإنجليزية افتراضيًا: Reset layout، وليس Reset Layout أو RESET LAYOUT. حافظ على أسماء العلم والاختصارات الراسخة. اتبع عرف المنصة، مثل نمط العناوين (title case) في أوامر القوائم الأصلية، فقط عندما يستفيد تكامل المنصة منه، وطبّقه باتساق داخل فئة المكوّن.

يمكن أن تمنح الأحرف الكبيرة كلها (ALL CAPS) تأكيدًا طباعيًا معتدلًا لتسميات الأقسام القصيرة جدًا، والعناوين التمهيدية، والحالات، والاختصارات الراسخة، والمعرّفات الشبيهة بالشيفرة. فشكلها المدمج وتباعدها المحسوب قد يكوّنان مستوى يشبه الخط العريض، لكنها لا تناسب الأزرار ولا العناوين الطويلة ولا الجمل ولا القوائم الكثيفة. لا تجمع بين الأحرف الكبيرة واللون القوي والخط العريض في المنطقة نفسها، ولا تحوّل كل سلسلة نصية تلقائيًا: يجب أن تحافظ أسماء المنتجات والاختصارات والمحتوى المترجم على حالة أحرفها المقصودة.

التسميات والأزرار وعناصر القوائم وعلامات التبويب والعناوين ونصوص الإرشاد والحالات القصيرة لا تنتهي بنقطة. أما الجمل الكاملة الشارحة والتحذيرية وجمل الأخطاء فتنتهي بها. تجنّب علامات التعجب في رسائل النجاح والفشل الاعتيادية. في الصينية، استخدم علامات الترقيم كاملة العرض في الجمل واحذف علامة الترقيم الختامية من تسميات عناصر التحكم القصيرة وفق القاعدة الدلالية نفسها.

استخدم محرف علامة الحذف الواحد (…) لا ثلاث نقاط. أضفه إلى كل زر أو عنصر قائمة يفتح حوارًا أو ورقة أو نافذة منفصلة، وإلى كل أمر يتطلب إدخالًا أو خيارات إضافية قبل أن يكتمل، مثل Settings… أو Export…. الأمر الذي يُنفَّذ فورًا لا يأخذ علامة حذف. استخدم مؤشر تقدم غير محدد لا نقاطًا زخرفية لإيصال أن العمل جارٍ.

يجب أن تقول الأخطاء ما حدث، وعند الفائدة إجراء الاسترداد التالي. ولا تذكر ملاحظات النجاح الحالة الناتجة إلا إذا لم تكن مرئية أصلًا. فضّل Couldn’t save. Check your connection and try again. على رمز تقني أو اعتذار طويل؛ واحذف إشعار Saved successfully العابر عندما يصبح المستند محفوظًا بصريًا.

#التدويل وملاءمة المنصة

يجوز أن تبقى أسماء وتسميات إمكانية الوصول (A11y) بالإنجليزية؛ فالترجمة غير مطلوبة لهذه السلاسل. احتفظ بتسمية العنصر نفسها عند تسمية عنصر تحكم يعمل عليه، حتى لو كانت تلك التسمية مترجمة. لا ينطبق هذا الاستثناء على نصوص الواجهة المرئية.

يجب أن يتحمل النص التمدد، وطباعة CJK، والكتابات من اليمين إلى اليسار، واختلاف تدوين الاختصارات. لا تحدّد حجم عنصر التحكم من تسمية إنجليزية واحدة. أبقِ النص خارج الأصول النقطية، وتجنّب ربط الأجزاء المترجمة، ودع التسميات تلتف أو تُقتطع فقط حيث يحدد المنتج مسار استرداد مثل تلميح الأداة.

احترم اختلافات المنصات التي تحمل معنى: Command مقابل Control، وزخارف النوافذ الأصلية، ومظهر النظام، وسلوك أشرطة التمرير، والقوائم، وإمكانات الإشعارات، وعلى الهواتف رأس النظام وإيماءة الرجوع والتنبيهات. حافظ على ثبات بنية المعلومات في المنتج عبر المنصات، لكن لا تمحُ السلوك المألوف للمنصة من أجل تطابق بكسلات سطحي.

تعمل تطبيقات NSH على الهواتف والأجهزة اللوحية وأجهزة سطح المكتب والويب، وغالبًا داخل تطبيق مضيف. صمّم لكل منها:

  • تحتفظ واجهة النظام بمظهر المنصة. على iOS وAndroid يكون رأس الصفحة هو رأس النظام (NavPage الخاص بـ title و خيارات الرأس)؛ أما أجهزة سطح المكتب والويب فتعرض فتحة header في الصفحة، ويرسمها التطبيق. وفّر الاثنين لكل صفحة (Navigation). تستخدم التأكيدات AlertDialog؛ ويتبع شريط الحالة الصفحة أو الحوار الأعلى (statusBar).
  • سمة المضيف. يمكن إعطاء التطبيق المضمَّن سمة مضيفه؛ وتتبعها الفئات وروابط Rust التي تقرأ الرموز المميزة بصورة أصلية (Theme tokens).
  • المناطق الآمنة ولوحة المفاتيح. أبقِ المحتوى داخل المنطقة الآمنة (env(safe-area-inset-*)، safeAreaInsets()) وحقول الإدخال فوق لوحة المفاتيح البرمجية (env(keyboard-inset-height)) (Device and environment).
  • المظهر وحجم النص والحركة. يجب أن تعمل كل واجهة في الوضعين الفاتح والداكن (dark:، colorScheme())، وبحجم النص الذي يختاره المستخدم، ومع تقليل الحركة (motion-reduce:، reduceMotion()).
  • من اليمين إلى اليسار. يتبع اتجاه التخطيط اللغة المحلية، ويحدده النمط direction لعرض ما ولشجرته الفرعية؛ وتنعكس خصائص البداية والنهاية (Right to left).
  • اللمس والمؤشر. أهداف اللمس لا تقل عن 44 × 44 نقطة؛ ولا يوجد التمرير بالمؤشر وتلميحات الأدوات إلا مع وجود مؤشر.
use nsh_base::nav_page;

nav_page()
    .title("Inbox")
    .header(page_header("Inbox"))
    .child(inbox())

#إرشادات للواجهات المولَّدة بالذكاء الاصطناعي

على الذكاء الاصطناعي الذي يعدّل واجهة NSH أن يفحص أولًا أقرب ميزة، ومكونات التطبيق ورموز السمة المميزة، و وثائق المكونات وتصريحاتها (.nsh/types). وعليه أن يحدد المهمة الأساسية، ومالك الحالة، وتركيب المكونات، ومساري لوحة المفاتيح واللمس قبل توليد الشيفرة. ولا يجوز له استنتاج واجهة API من أمثلة React أو React Native أو Shadcn، ولا اختراع خاصية أو دالة في NSH لمجرد أن الاسم يبدو معقولًا؛ إذ يبلّغ nsh check وفحص الفئات في البناء عمّا هو غير موجود.

يبقى ناتج الذكاء الاصطناعي ناقصًا حتى يتمكن إنسان من شرح سبب انتماء التسلسل الهرمي والكثافة واختيار المكونات والقيم الحرفية الاستثنائية إلى هذا المنتج. ولقطة الشاشة المقنعة بصريًا ليست دليلًا: فسلوك لوحة المفاتيح واللمس، والتركيز، والمحتوى الديناميكي، والسمات، وتغيير الحجم، وحجم النص، وحالات الفشل، كلها جزء من التصميم.

#قائمة التحقق من إمكانية الوصول

قبل اعتبار الشاشة مكتملة، تحقق مما يلي:

  • يمكن الوصول إلى كل إجراء وتشغيله بلوحة المفاتيح واللمس وقارئ الشاشة؛
  • ترتيب التركيز يتبع الترتيب البصري وترتيب المهمة؛
  • يبقى التركيز مرئيًا ويُستعاد بعد الطبقات المنبثقة؛
  • لعناصر التحكم أسماء يمكن الوصول إليها؛ وتستخدم عناصر التحكم المعتمدة على الأيقونة وحدها تلميحات أدوات عندما يحتاج معناها أو نطاقها أو اختصارها إلى شرح؛
  • للنص والحدود ذات المعنى تباين كافٍ؛
  • لا تُنقل الحالة باللون وحده؛
  • يمكن التمييز بين الحالتين المعطّلة والمقروءة فقط؛
  • تبقى التسميات والأخطاء والأوصاف قرب عناصر التحكم الخاصة بها؛
  • يبقى المحتوى قابلًا للاستخدام مع الترجمات الأطول، ومن اليمين إلى اليسار، والنص الأكبر؛
  • أهداف اللمس لا تقل عن 44 × 44 نقطة، وأهداف المؤشر ذات حجم مريح حتى في التخطيط الكثيف.

#قائمة مراجعة التصميم

المراجعة لا تحصي المكونات؛ بل تحكم على ما إذا كانت الواجهة اتخذت القرارات الصحيحة. اسأل بالترتيب:

  1. هل المهمة واضحة؟ هل يستطيع المستخدم الجديد إدراك الغرض والإجراء الأساسي والخطوة التالية دون تعلّم أو تخمين أو تجريب؟
  2. هل يفي كل إجراء بوعده؟ هل تصف التسمية وعنصر التحكم والحالة والنطاق والملاحظات والنتيجة نتيجة واحدة متسقة؟
  3. هل التسلسل الهرمي حاسم ومنضبط؟ هل تنال الميزة الأساسية المساحة التي تستحقها بينما تظل الألوان القوية والخط العريض والشارات والتنبيهات والأزرار الأساسية نادرة؟
  4. هل يمكن للواجهة أن تفعل أقل وبصورة أفضل؟ هل يمكن حذف نقطة دخول أو خيار أو حالة، أو دمجها، أو تأجيلها دون إضعاف المهمة الكاملة؟
  5. هل البنية دقيقة؟ هل تتشارك العناصر المتناظرة محاور المحاذاة، وتبقى الفجوات المتساوية متساوية حتى البكسل المعروض، وتقع أشرطة التمرير عند حافة منطقة التمرير الفعلية؟
  6. هل تتبع نظام المكونات؟ هل تحتفظ عناصر التحكم القياسية بهندستها وحالاتها وسلوك لوحة المفاتيح ونموذج الإغلاق، مع توفير المظهر عبر رموز السمة والمقياس؟
  7. هل تبقى قابلة للاستخدام في كل حالة وقيد؟ تحقق من سلوك لوحة المفاتيح واللمس والتركيز، وحالات الفراغ والتحميل والفشل والأذونات، والترجمات الأطول، والنص الأكبر، ومن اليمين إلى اليسار، والمظهرين، وأدنى حجم للنافذة، وتقليل الحركة.
  8. هل اختُبرت على منصات حقيقية؟ أكمل المهمة بمكونات حقيقية ونصوص حقيقية ومحتوى تمثيلي على كل منصة تُشحن إليها (nsh run --target …، nsh test --target …)، وليس بلقطة شاشة مثالية فقط.