ساختن یک جزء با دکمه تقسیم

یک نمای کلی از نحوه ساخت یک جزء کلیدی قابل دسترسی.

در این پست می‌خواهم فکری را در مورد روشی برای ساخت دکمه تقسیم به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید .

نسخه ی نمایشی

اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:

نمای کلی

دکمه های تقسیم دکمه هایی هستند که یک دکمه اصلی و لیستی از دکمه های اضافی را پنهان می کنند. آنها برای افشای یک عمل معمولی مفید هستند، در حالی که اقدامات ثانویه و کمتر مورد استفاده را تا زمانی که نیاز باشد، تودرتو می‌کنند. یک دکمه تقسیم می تواند برای کمک به یک طراحی شلوغ بسیار مهم باشد. یک دکمه تقسیم پیشرفته حتی ممکن است آخرین اقدام کاربر را به خاطر بسپارد و آن را به موقعیت اصلی ارتقا دهد.

یک دکمه تقسیم مشترک را می توان در برنامه ایمیل شما پیدا کرد. اقدام اصلی ارسال است، اما شاید بتوانید بعداً ارسال کنید یا به جای آن یک پیش نویس ذخیره کنید:

نمونه ای از دکمه تقسیم همانطور که در یک برنامه ایمیل دیده می شود.

منطقه اقدام مشترک خوب است، زیرا کاربر نیازی به نگاه کردن به اطراف ندارد. آنها می دانند که اقدامات ضروری ایمیل در دکمه تقسیم وجود دارد.

قطعات

اجازه دهید قبل از بحث در مورد هماهنگی کلی و تجربه کاربر نهایی، بخش‌های اساسی یک دکمه تقسیم را بشکنیم. ابزار بازرسی دسترسی VisBug در اینجا برای کمک به نمایش یک نمای کلان از مؤلفه، نمایاندن جنبه‌های HTML، سبک و دسترسی برای هر بخش اصلی استفاده می‌شود.

عناصر HTML که دکمه تقسیم را تشکیل می دهند.

ظرف دکمه تقسیم سطح بالا

کامپوننت بالاترین سطح یک فلکس باکس درون خطی است، با یک کلاس از 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%);
  }
}

طرح بندی و رنگ

نشانه گذاری

عنصر به صورت <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 Nesting و انتخابگر تابعی :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 یک لیست دکمه کارت شناور است که از ویژگی‌های سفارشی و واحدهای نسبی استفاده می‌کند تا به‌طور نامحسوس کوچک‌تر باشد، به‌طور تعاملی با دکمه اصلی تطبیق داده شود، و در برند با استفاده از رنگ استفاده شود. توجه داشته باشید که نمادها کنتراست کمتری دارند، نازک‌تر هستند و سایه رنگ آبی کاملاً روی آن دیده می‌شود. مانند دکمه ها، UI و UX قوی نتیجه این جزئیات کوچک است.

یک عنصر کارت شناور.

.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%))
  ;
}

به نمادها و دکمه‌ها رنگ‌های نام تجاری داده می‌شود تا در هر کارت با مضمون تیره و روشن به زیبایی استایل دهید:

پیوندها و نمادها برای پرداخت، پرداخت سریع، و ذخیره برای بعد.

.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> عمومی

همه نمادها با استفاده از واحد ch به‌عنوان inline-size اندازه‌ی نسبی به font-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 shorthand، از مزایای padding اضلاع منطقی بهره مند شوید. - border-end-start-radius و دوستان بر اساس جهت سند، گوشه ها را گرد می کنند. - inline-size به جای width تضمین می کند که اندازه به ابعاد فیزیکی مرتبط نیست. - border-inline-start یک حاشیه به شروع اضافه می کند که بسته به جهت اسکریپت ممکن است در سمت راست یا چپ باشد.

جاوا اسکریپت

تقریباً تمام جاوا اسکریپت زیر برای افزایش دسترسی است. دو تا از کتابخانه‌های کمکی من برای ساده‌تر کردن کارها استفاده می‌شوند. 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

در حالی که از نظر بصری آشکار است که یک پنجره بازشو نشان داده و پنهان می شود، یک صفحه خوان به چیزی بیش از نشانه های بصری نیاز دارد. جاوا اسکریپت در اینجا برای تمجید از تعامل مبتنی بر CSS :focus-within با جابجایی ویژگی مناسب صفحه‌خوان استفاده می‌شود.

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)
})

نتیجه گیری

حالا که می دانید من چگونه این کار را انجام دادم، چگونه این کار را انجام می دهید‽🙂

بیایید رویکردهایمان را متنوع کنیم و همه راه‌های ساخت در وب را بیاموزیم. یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!

ریمیکس های انجمن