Tworzenie komponentu złożonego z przycisku

Podstawowe omówienie tworzenia łatwo dostępnego komponentu podziału.

W tym poście chcę pokazać, jak opracować przycisk podziału . Wypróbuj wersję demonstracyjną.

Demonstracja
.

Jeśli wolisz film, oto wersja tego posta w YouTube:

Omówienie

Przyciski podzielone to przyciski. ukrywające przycisk główny i listę dodatkowych przycisków. Są przydatne do ujawniania wspólnego działania przy zagnieżdżaniu drugorzędnych, rzadziej używanych i wykonywać odpowiednie czynności. Przycisk podziału może mieć kluczowe znaczenie przy projektowaniu w niewielkim stopniu. Zaawansowany przycisk podziału może nawet zapamiętać ostatnie działanie użytkownika. i awansuje ją na główną pozycję.

Wspólny przycisk podziału można znaleźć w aplikacji do obsługi poczty e-mail. Działanie główne możesz wysłać później lub zapisać wersję roboczą:

Przykładowy przycisk podziału widoczny w aplikacji do obsługi poczty e-mail.

Wspólny obszar działania jest atrakcyjny, ponieważ użytkownik nie musi się rozglądać. Ta pamiętaj, że kluczowe działania związane z e-mailami znajdują się na przycisku podziału.

Części

Omówmy najważniejsze elementy podzielonego przycisku, zanim omówimy ich funkcje. nad ogólną administracją i wrażeniami użytkownika. Ułatwienia dostępu w aplikacji VisBug które pozwala wyświetlić widok makro komponentu, aspekty HTML, stylu i ułatwień dostępu.

Elementy HTML tworzące przycisk podziału.

Kontener przycisku podziału najwyższego poziomu

Komponent najwyższego poziomu to wbudowany Flexbox o klasie gui-split-button zawierający działanie główne oraz .gui-popup-button.

Klasa gui-split-button została sprawdzona i wyświetla właściwości CSS używane w tej klasie.

Główny przycisk polecenia

Początkowo widoczny i możliwy do zaznaczenia element <button> mieści się w kontenerze z dwa pasujące kształty narożników dla fokus, najedź i aktywne interakcje w wyświetlane w folderze .gui-split-button.

Inspektor pokazujący reguły CSS elementu przycisku.

Przycisk przełączania wyskakującego okienka

Przycisk wyskakującego okienka Element pomocniczy służy do aktywowania i kojarzenia z listą dodatkowe przyciski. Zwróć uwagę, że to nie jest obiekt <button> i nie można go zaznaczyć. Pamiętaj jednak: jest to kotwica do pozycjonowania dla .gui-popup, a host dla :focus-within używany aby wyświetlić wyskakujące okienko.

Inspektor wyświetlający reguły CSS dla klasy gui-popup-button.

Wyskakująca karta

To jest element podrzędny karty pływającej do jej reklamy zakotwiczonej .gui-popup-button, pozycja bezwzględna i semantycznie zawijać listę przycisków.

Inspektor pokazujący reguły CSS dla wyskakującego okienka klasy

Działania dodatkowe

Element <button> z możliwością zaznaczenia, trochę mniejszy rozmiar czcionki niż podstawowa przycisk polecenia, który zawiera ikonę i dodatkowy do przycisku głównego.

Inspektor pokazujący reguły CSS elementu przycisku.

Właściwości niestandardowe

Poniższe zmienne pomagają w harmonijności kolorów i stanowią centralny punkt modyfikować wartości używane w całym komponencie.

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

Układy i kolory

Markup

Element zaczyna się od elementu <div> i ma niestandardową nazwę klasy.

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

Dodaj przycisk główny i elementy .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>

Zwróć uwagę na atrybuty aria aria-haspopup i aria-expanded. Te wskazówki to są kluczowe dla czytników ekranu, ponieważ muszą znać możliwości i stan podziału za pomocą przycisku. Atrybut title jest przydatny dla wszystkich.

Dodaj ikonę <svg> i element kontenera .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>

Aby umieścić proste wyskakujące okienko, .gui-popup jest elementem podrzędnym wobec przycisku, który powoduje jego rozwinięcie. Jedynym ograniczeniem w przypadku tej strategii jest .gui-split-button. kontener nie może użyć polecenia overflow: hidden, ponieważ spowoduje to obcięcie wyskakującego okienka obecny wizualnie.

Element <ul> zawierający treści <li><button> zostanie ogłoszony jako „przycisk” lista” przez czytniki ekranu, czyli właśnie za pomocą interfejsu.

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

Do przycisków dodatkowych dodaliśmy ikony, aby zwiększyć wystrój i cieszyć się kolorami ze strony https://heroicons.com. Ikony są opcjonalne w obu przypadkach oraz przycisk główny i dodatkowy.

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

Style

Po umieszczeniu kodu HTML i treści style są gotowe do wyboru kolorów i układu.

Styl kontenera przycisku podziału

Typ wyświetlania inline-flex dobrze sprawdza się w przypadku tego komponentu opakowań, ponieważ powinny pasować do innych podzielonych przycisków, działań lub elementów.

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

Przycisk podziału

Styl <button>

Przyciski bardzo dobrze ukrywają ilość kodu. Może być konieczne cofania lub zastępowania stylów domyślnych przeglądarki, ale trzeba też wymusić dziedziczenia, dodawać stany interakcji i dostosowywać do różnych preferencji użytkowników typów danych wejściowych. Style przycisków szybko się sumują.

Różnią się one od zwykłych przycisków, bo mają wspólne tło. z elementem nadrzędnym. Zazwyczaj przycisk ma własny kolor tła i tekstu. Te osoby je jednak udostępniają i stosują swoje własne tło tylko w przypadku interakcji.

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

Dodawanie stanów interakcji za pomocą kilku stylów CSS pseudoklasy i użycie dopasowywania właściwości niestandardowe dla stanu:

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

Przycisk główny musi mieć kilka specjalnych stylów, aby uzyskać pełny efekt projektowy:

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

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

Przycisk i ikona jasnego motywu shadow:

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

Świetny przycisk zwraca uwagę na mikrointerakcje i drobne szczegóły.

Uwaga na temat miejsca :focus-visible

Zwróć uwagę, że w stylach przycisków występuje wartość :focus-visible zamiast :focus. :focus ma kluczowe znaczenie dla stworzenia łatwo dostępnego interfejsu, ale ma taki upadek: nie chodzi o to, czy użytkownik musi to zobaczyć czy nie ale nie, dotyczy dowolnego zaznaczenia.

Poniższy film pokazuje, w jaki sposób można podzielić tę mikrointerakcję, aby pokazać, :focus-visible to inteligentna alternatywa.

Określanie stylu przycisku wyskakującego okienka

Flexbox 4ch do wyśrodkowania ikony i zakotwiczenia listy przycisków wyskakującej. Polub przycisk główny, jest przezroczysty, dopóki nie najedzie kursorem na inny przycisk lub nie wykonuje dodatkowej czynności. i rozciągnięte, by wypełnić.

Część przycisku ze strzałką służąca do wyzwalania wyskakującego okienka.

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

Nakładaj warstwy na elementy po najechaniu kursorem, zaznaczenie i stan aktywności za pomocą CSS Zagnieżdżenie oraz Funkcjonalny selektor :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);
  }
}

Te style są głównym elementem przykuwania uwagi do wyświetlania i ukrywania wyskakującego okienka. Gdy .gui-popup-button ma wartość focus na dowolnym ze swoich elementów podrzędnych, ustawiono opacity, pozycję i pointer-events na ikonie i w wyskakującym okienku.

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

Ostatnim elementem jest warunkowe określenie stylu przekształcenia w zależności od preferencji ruchu użytkownika:

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

Użytkownicy, którzy nie spojrzyli na ten kod, zauważą, że przezroczystość nadal została zmieniona. którzy preferują mniej ruchu.

Określanie stylu wyskakującego okienka

Element .gui-popup to przycisk karty pływającej, który korzysta z właściwości niestandardowych. i jednostki względne, aby były nieznacznie mniejsze, interaktywnie dopasowywane do i markę za pomocą koloru. Ikony mają słabszy kontrast. są cieńsze, a cień jest ciemną nutą marki. Podobnie jak w przypadku przycisków, Połączenie tych drobnych detali to dobry interfejs i wygoda użytkowania.

Element pływającej karty.

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

Ikony i przyciski mają odpowiedni kolor marki, aby pasowały do każdego ciemnego koloru i karta o jasnej tematyce:

Linki i ikony płatności, Szybkiej płatności i Zapisz na później.

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

Wyskakujące okienko ciemnego motywu zawiera cienie tekstu i ikon oraz intensywny cień ramki:

Wyskakujące okienko w ciemnym motywie.

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

Ogólne style ikony <svg>

Wszystkie ikony mają stosowny rozmiar do przycisku font-size, w którym są używane przez za pomocą jednostki ch jako inline-size. Do każdego typu dodano też style, które ułatwiają zarysowanie ikon płynne.

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

Układ od prawej do lewej

Właściwości logiczne wykonują wszystkie złożone zadania. Oto lista użytych właściwości logicznych: – display: inline-flex tworzy wbudowany element elastyczny. - padding-block i padding-inline jako para, a nie padding Krótko mówiąc, warto skorzystać z dopełnienia logiki po bokach. - border-end-start-radius i znajomi będą zaokrąglonych rogów w zależności od kierunku dokumentu. – Dzięki parametrowi inline-size zamiast width rozmiar nie jest powiązany z wymiarami fizycznymi. – border-inline-start dodaje obramowanie na początku, które może znajdować się po prawej lub lewej stronie w zależności od kierunku skryptu.

JavaScript

Prawie cały poniższy kod JavaScript ma na celu zwiększenie dostępności. Dwie są też używane biblioteki pomocnicze, aby ułatwić wykonywanie zadań. BlingBlingJS jest używany do zwięzłości, zapytań DOM i łatwej konfiguracji detektora zdarzeń, roving-ux ułatwia ułatwienie dostępu interakcje z klawiaturą i padem do gier w wyskakującym okienku.

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

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

Po zaimportowaniu powyższych bibliotek oraz wybraniu i zapisaniu elementów zmiennych, uaktualnienie doświadczenia zajmie tylko kilka funkcji.

Indeks Roving

Gdy zaznaczysz element .gui-popup-button na klawiaturze lub czytniku ekranu, chcemy przenieść zaznaczenie na pierwszy (lub ostatnio zaznaczony) przycisk na pasku .gui-popup Pomaga nam w tym biblioteka za pomocą element i target .

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

Element przekazuje teraz zaznaczenie na docelowe elementy podrzędne (<button>) i umożliwia włączenie standardowego klawisza strzałki, by przeglądać opcje.

Przełączam: aria-expanded

Choć wyraźnie widać, że wyskakujące okienko wyświetla się i ukrywa, czytnik ekranu potrzebuje więcej niż wskazówek wizualnych. Język JavaScript jest tu używany, aby uzupełnić interakcję :focus-within opartą na CSS przez przełączenie odpowiedniego atrybutu dla czytnika ekranu.

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

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

Włączam klawisz Escape

W centrum uwagi użytkownika znajduje się pułapka, co oznacza, że musimy muszą umożliwiać wyjście z domu. Najczęstszym sposobem jest zezwolenie na użycie klucza Escape. Zwróć uwagę na naciśnięcia klawiszy na wyskakującej stronie, bo wszystkie zdarzenia na klawiaturze że dzieci pojawią się w dymkach obok tego rodzica.

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

Jeśli przycisk wyskakującego okienka zobaczy naciśnięcie dowolnego klawisza Escape, zaznaczenie zostanie usunięte z samego przycisku z blur()

Kliknięcia przycisku podziału

Wreszcie, jeśli użytkownik kliknie przycisk albo wejdzie z nim w interakcję z klawiaturą, aplikacja musi wykonać odpowiednie działanie. Dymek wydarzeń jest używany znowu tutaj, ale tym razem w kontenerze .gui-split-button, aby złapać przycisk. kliknięć z wyskakującego okienka podrzędnego lub działania głównego.

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

Podsumowanie

Wiesz już, jak to zrobiłem. Jak Ty? 🙂

Stosujmy różne podejścia i poznajmy sposoby budowania obecności w internecie. Utwórz wersję demonstracyjną, a potem dodaj linki do funkcji tweetuj mi. znajdziesz poniżej w sekcji z remiksami na karcie Społeczność.

Remiksy utworzone przez społeczność