بناء مكون انقسام الزر

نظرة عامة أساسية حول كيفية إنشاء مكون زر تقسيم يسهل الوصول إليه.

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

عرض توضيحي

إليك نسخة من هذه المشاركة على YouTube إذا كنت تفضّل ذلك:

نظرة عامة

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

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

مثال على زر تقسيم كما يظهر في تطبيق بريد إلكتروني.

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

الأجزاء

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

عناصر HTML التي يتكوّن منها زر التقسيم.

حاوية زر مقسَّم من المستوى الأعلى

المكون الأعلى مستوى هو flexbox مضمَّن، بفئة من gui-split-button، يحتوي على الإجراء الأساسي و.gui-popup-button

تم فحص فئة gui-split-button وعرض خصائص CSS المستخدمة في هذه الفئة.

زر الإجراء الأساسي

يتناسب <button> الذي يكون مرئيًا ويمكن التركيز عليه بشكل مبدئي داخل الحاوية التي تحتوي على شكلين متطابقين من الزوايا التركيز، التمرير التفاعلات النشطة من أجل مضمنة في .gui-split-button.

يعرض أداة الفحص قواعد CSS لعنصر الزر.

زر إيقاف النافذة المنبثقة

"زر النافذة المنبثقة" مخصص للتفعيل والإشارة إلى قائمة الأزرار الثانوية. لاحظ أنّها ليست <button> ولا يمكن التركيز عليها. ومع ذلك، هو علامة ارتساء تحديد الموضع لـ .gui-popup ومضيف :focus-within المستخدم لعرض النافذة المنبثقة.

يعرض أداة الفحص قواعد CSS للفئة gui-popup-button.

البطاقة المنبثقة

هذه بطاقة عائمة في شريط الارتساء .gui-popup-button، موضع مطلق بشكل دلالي لقائمة الأزرار.

أداة الفحص التي تعرض قواعد CSS لفئة gui-popup

الإجراء أو الإجراءات الثانوية

<button> يمكن التركيز عليه بحجم خط أصغر قليلاً من حجم الخط الأساسي زر إجراء على رمز وعلامة النمط إلى الزر الأساسي.

يعرض أداة الفحص قواعد CSS لعنصر الزر.

الخصائص المخصصة

تساعد المتغيرات التالية في خلق تناغم الألوان ومكان مركزي لتعديل القيم المستخدمة في المكون.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);

.gui-split-button {
  --theme:             hsl(220 75% 50%);
  --theme-hover:  hsl(220 75% 45%);
  --theme-active:  hsl(220 75% 40%);
  --theme-text:      hsl(220 75% 25%);
  --theme-border: hsl(220 50% 75%);
  --ontheme:         hsl(220 90% 98%);
  --popupbg:         hsl(220 0% 100%);

  --border: 1px solid var(--theme-border);
  --radius: 6px;
  --in-speed: 50ms;
  --out-speed: 300ms;

  @media (--dark) {
    --theme:             hsl(220 50% 60%);
    --theme-hover:  hsl(220 50% 65%);
    --theme-active:  hsl(220 75% 70%);
    --theme-text:      hsl(220 10% 85%);
    --theme-border: hsl(220 20% 70%);
    --ontheme:         hsl(220 90% 5%);
    --popupbg:         hsl(220 10% 30%);
  }
}

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

Markup

يبدأ العنصر كـ <div> باسم فئة مخصّصة.

<div class="gui-split-button"></div>

أضِف الزر الأساسي وعناصر .gui-popup-button.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>

لاحِظ سمتَي aria aria-haspopup وaria-expanded. هذه الإشارات هي لتعرف قارئات الشاشة على إمكانية وحالة التقسيم المستخدم. تكون السمة title مفيدة للجميع.

أضِف الرمز <svg> وعنصر الحاوية .gui-popup.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup"></ul>
  </span>
</div>

بالنسبة للموضع المنبثق المباشر، .gui-popup هو عنصر ثانوي للزر الذي وتوسيعه. الهدف الوحيد من هذه الاستراتيجية هو .gui-split-button. لا يمكن للحاوية استخدام overflow: hidden، لأنّ ذلك سيقطع النافذة المنبثقة التواجد بصريًا.

سيُعلن عن <ul> مليء بمحتوى <li><button> على أنّه "زر الْقَائِمَة إلى برامج قراءة الشاشة، وهي بالضبط الواجهة المقدمة.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li>
        <button>Schedule for later</button>
      </li>
      <li>
        <button>Delete</button>
      </li>
      <li>
        <button>Save draft</button>
      </li>
    </ul>
  </span>
</div>

للحصول على لمسة مميزة والاستمتاع بالألوان، أضفنا رموزًا إلى الأزرار الثانوية من https://heroicons.com تكون الرموز اختيارية لكليهما الزرين الأساسي والثانوي.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
        Schedule for later
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
        </svg>
        Delete
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
        </svg>
        Save draft
      </button></li>
    </ul>
  </span>
</div>

الأنماط

مع استخدام HTML والمحتوى، تكون الأنماط جاهزة لتوفير الألوان والتنسيق.

تصميم حاوية زر التقسيم

يعمل نوع العرض inline-flex بشكل جيد لمكوِّن الالتفاف هذا لأنّه أن تتناسب مع الأزرار أو الإجراءات أو العناصر الأخرى

.gui-split-button {
  display: inline-flex;
  border-radius: var(--radius);
  background: var(--theme);
  color: var(--ontheme);
  fill: var(--ontheme);

  touch-action: manipulation;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

زر التقسيم

التصميم <button>

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

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

.gui-split-button button {
  cursor: pointer;
  appearance: none;
  background: none;
  border: none;

  display: inline-flex;
  align-items: center;
  gap: 1ch;
  white-space: nowrap;

  font-family: inherit;
  font-size: inherit;
  font-weight: 500;

  padding-block: 1.25ch;
  padding-inline: 2.5ch;

  color: var(--ontheme);
  outline-color: var(--theme);
  outline-offset: -5px;
}

إضافة حالات التفاعل باستخدام بعض CSS فئات زائفة واستخدام مطابقة الخصائص المخصصة للولاية:

.gui-split-button button {
  

  &:is(:hover, :focus-visible) {
    background: var(--theme-hover);
    color: var(--ontheme);

    & > svg {
      stroke: currentColor;
      fill: none;
    }
  }

  &:active {
    background: var(--theme-active);
  }
}

يحتاج الزر الأساسي إلى بعض الأنماط الخاصة لإكمال تأثير التصميم:

.gui-split-button > button {
  border-end-start-radius: var(--radius);
  border-start-start-radius: var(--radius);

  & > svg {
    fill: none;
    stroke: var(--ontheme);
  }
}

وأخيرًا، للحصول على بعض الرموز التعبيرية، يحصل الزر "مظهر فاتح" والرمز على الظل:

.gui-split-button {
  @media (--light) {
    & > button,
    & button:is(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--theme-active);
    }
    & > .gui-popup-button > svg,
    & button:is(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--theme-active));
    }
  }
}

اهتم زر رائع بالتفاعلات الدقيقة والتفاصيل الدقيقة.

ملاحظة حول :focus-visible

لاحِظ كيف تستخدم أنماط الأزرار :focus-visible بدلاً من :focus. :focus لمسة مهمة لإنشاء واجهة مستخدم يسهل الوصول إليها ولكنها لا غنى عنها السقوط: لا يتعلق الأمر بذكاء بشأن ما إذا كان المستخدم بحاجة إلى رؤيته أم لا لا، فسينطبق على أي تركيز.

يحاول الفيديو أدناه تحليل هذا التفاعل الدقيق لإظهار كيف :focus-visible هو بديل ذكي.

تصميم زر النافذة المنبثقة

شريط مرن 4ch لتوسيط رمز ووضع قائمة بأزرار منبثقة أعجبني الزر الأساسي، فهو يظل شفافًا حتى يتم تمريره أو التفاعل معه بطريقة أخرى وتمديده لملئه.

جزء السهم في زر التقسيم المُستخدَم لتشغيل النافذة المنبثقة.

.gui-popup-button {
  inline-size: 4ch;
  cursor: pointer;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-inline-start: var(--border);
  border-start-end-radius: var(--radius);
  border-end-end-radius: var(--radius);
}

وضع طبقة التمرير والتركيز والحالات النشطة باستخدام CSS يُعد التداخل أداة الاختيار الوظيفية في :is():

.gui-popup-button {
  

  &:is(:hover,:focus-within) {
    background: var(--theme-hover);
  }

  /* fixes iOS trying to be helpful */
  &:focus {
    outline: none;
  }

  &:active {
    background: var(--theme-active);
  }
}

تشكّل هذه الأنماط عنصر الجذب الأساسي لعرض النافذة المنبثقة وإخفائها. عندما .gui-popup-button لديه focus على أي من عناصره الثانوية، تم ضبط opacity، الموضع وpointer-events، على الرمز والنافذة المنبثقة.

.gui-popup-button {
  

  &:focus-within {
    & > svg {
      transition-duration: var(--in-speed);
      transform: rotateZ(.5turn);
    }
    & > .gui-popup {
      transition-duration: var(--in-speed);
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }
  }
}

عند اكتمال نمطي الدخول والخروج، تكون القطعة الأخيرة هي مشروطة تحويلات الانتقال اعتمادًا على تفضيل المستخدم للحركة:

.gui-popup-button {
  

  @media (--motionOK) {
    & > svg {
      transition: transform var(--out-speed) ease;
    }
    & > .gui-popup {
      transform: translateY(5px);

      transition:
        opacity var(--out-speed) ease,
        transform var(--out-speed) ease;
    }
  }
}

ستلاحظ مراقبة شدة باستخدام الرمز أنّ معدل الشفافية لا يزال منقولاً للمستخدمين. الذين يفضلون الحركة المنخفضة.

تصميم النافذة المنبثقة

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

عنصر بطاقة عائم

.gui-popup {
  --shadow: 220 70% 15%;
  --shadow-strength: 1%;

  opacity: 0;
  pointer-events: none;

  position: absolute;
  bottom: 80%;
  left: -1.5ch;

  list-style-type: none;
  background: var(--popupbg);
  color: var(--theme-text);
  padding-inline: 0;
  padding-block: .5ch;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: .9em;
  transition: opacity var(--out-speed) ease;

  box-shadow:
    0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
    0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
    0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
    0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
    0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
    0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
  ;
}

يتم إعطاء الرموز والأزرار ألوان علامة تجارية لتتلاءم بشكل جيد داخل كل ظلام وبطاقة ذات مظهر فاتح:

روابط ورموز للدفع وQuick Pay والحفظ لوقت لاحق.

.gui-popup {
  

  & svg {
    fill: var(--popupbg);
    stroke: var(--theme);

    @media (prefers-color-scheme: dark) {
      stroke: var(--theme-border);
    }
  }

  & button {
    color: var(--theme-text);
    width: 100%;
  }
}

تحتوي النافذة المنبثقة للمظهر الداكن على إضافات ظلال نصوص ورموز، بالإضافة إلى إظهار ظل المربع بشدة:

النافذة المنبثقة ذات المظهر الداكن.

.gui-popup {
  

  @media (--dark) {
    --shadow-strength: 5%;
    --shadow: 220 3% 2%;

    & button:not(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--ontheme);
    }

    & button:not(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--ontheme));
    }
  }
}

أنماط رموز <svg> العامة

يبلغ حجم جميع الرموز نسبيًا للزر font-size الذي يتم استخدامها من خلاله باستخدام وحدة ch inline-size كما يتم تخصيص بعض الأنماط لكل منها للمساعدة في تحديد الرموز الناعمة بسلاسة.

.gui-split-button svg {
  inline-size: 2ch;
  box-sizing: content-box;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}

التخطيط من اليمين إلى اليسار

السمات المنطقية تؤدي جميع الأعمال المعقّدة. في ما يلي قائمة الخصائص المنطقية المستخدمة: - ينشئ display: inline-flex عنصرًا مرنًا مضمَّنًا. - padding-block وpadding-inline كزوج، بدلاً من padding للحصول على فوائد المساحة المتروكة حول الجوانب المنطقية. - border-end-start-radius و سيقوم الأصدقاء الزوايا المستديرة بناءً على اتجاه المستند. - يضمن استخدام السمة inline-size بدلاً من width عدم ربط المقاس بالأبعاد المادية. - يضيف border-inline-start حدًا إلى البداية، والذي قد يكون على اليمين أو اليسار بناءً على اتجاه النص البرمجي.

JavaScript

تهدف معظم رموز JavaScript التالية إلى تحسين إمكانية الوصول. اثنان من استخدام المكتبات المساعدة لتسهيل المهام قليلاً. يتم استخدام BlingBlingJS للعبارات الموجزة. طلبات بحث DOM وإعداد أدوات معالجة الأحداث بسهولة، مع تساعد roving-ux في تسهيل تفاعلات لوحة المفاتيح ولوحة الألعاب للنافذة المنبثقة.

import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'

const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')

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

المؤشر المتنقل

عندما تركِّز لوحة مفاتيح أو قارئ شاشة على .gui-popup-button، نريد إعادة توجيه التركيز إلى الزر الأول (أو الأحدث) في .gui-popup تساعدنا المكتبة في تنفيذ ذلك باستخدام element وtarget. المعلَمات.

popupButtons.forEach(element =>
  rovingIndex({
    element,
    target: 'button',
  }))

يوجِّه العنصر الآن التركيز إلى العناصر الثانوية المستهدَفة في <button> ويفعِّل التنقل بمفتاح السهم القياسي لتصفح الخيارات.

جارٍ تبديل aria-expanded

بينما من الواضح بصريًا أن النافذة المنبثقة تظهر وتخفي، يحتاج قارئ الشاشة إلى أكثر من الإشارات المرئية. يتم استخدام JavaScript هنا ليتكامل مع تفاعل :focus-within المستند إلى CSS من خلال تبديل سمة مناسبة لقارئ الشاشة.

popupButtons.on('focusin', e => {
  e.currentTarget.setAttribute('aria-expanded', true)
})

popupButtons.on('focusout', e => {
  e.currentTarget.setAttribute('aria-expanded', false)
})

جارٍ تفعيل المفتاح Escape

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

popupButtons.on('keyup', e => {
  if (e.code === 'Escape')
    e.target.blur()
})

إذا لاحظ الزر المنبثق أي ضغط على مفتاح Escape، سيؤدي ذلك إلى إزالة التركيز من نفسه. مع blur()

عدد النقرات على زر التقسيم

وأخيرًا، إذا نقر المستخدم على الأزرار أو ينقر عليها أو تفاعل معها لوحة المفاتيح، تطبيق يحتاج إلى تنفيذ الإجراء المناسب. يتم استخدام فقاعات الأحداث مرة أخرى هنا، ولكن هذه المرة على حاوية .gui-split-button، لالتقاط الزر النقرات من نافذة منبثقة فرعية أو من الإجراء الأساسي

splitButtons.on('click', event => {
  if (event.target.nodeName !== 'BUTTON') return
  console.info(event.target.innerText)
})

الخاتمة

الآن بعد أن تعرّفت على كيفية إجراء ذلك، كيف يمكنك‽ 🙂

يمكننا تنويع أساليبنا وتعلُّم جميع طرق إنشاء المحتوى على الويب. أنشئ عرضًا توضيحيًا وأضيف روابط تغريدة إليّ. انتقِل إلى قسم الريمكسات في المنتدى أدناه.

ريمكسات من إنشاء المنتدى