Создание компонента с множественным выбором

Базовый обзор того, как создать отзывчивый, адаптивный и доступный компонент с множественным выбором для сортировки и фильтрации пользовательского опыта.

В этой публикации я хочу поделиться мыслями о создании компонента с множественным выбором. Попробуйте демо .

Демо

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

Пользователям часто предлагаются элементы, иногда даже множество , и в таких случаях может быть полезно предоставить способ сократить список, чтобы избежать перегрузки выбором . В этой публикации рассматривается фильтрация пользовательского интерфейса как способ сокращения выбора. Это достигается за счёт предоставления атрибутов элементов, которые пользователи могут выбрать или отменить, что сокращает количество результатов и, следовательно, снижает перегрузку выбором.

Взаимодействия

Цель — обеспечить быстрый доступ к параметрам фильтрации для всех пользователей с различными типами ввода. Это будет реализовано с помощью пары адаптивных и отзывчивых компонентов. Традиционная боковая панель с флажками для настольных компьютеров, клавиатур и экранных ридеров, а также элемент <select multiple> для пользователей сенсорных устройств.

Сравнительный скриншот, показывающий светлую и тёмную темы рабочего стола с боковой панелью флажков, с мобильными устройствами iOS и Android с элементом множественного выбора.

Такое решение использовать встроенный множественный выбор для сенсорного экрана, а не для настольного компьютера, экономит и создает работу, но, на мой взгляд, обеспечивает соответствующий опыт с меньшим объемом кода, чем при разработке всего адаптивного интерфейса в одном компоненте.

Трогать

Компонент сенсорного ввода экономит место и повышает точность взаимодействия пользователя с мобильным устройством. Он экономит место, сворачивая целую боковую панель флажков во встроенный сенсорный элемент <select> . Он повышает точность ввода, отображая большой сенсорный элемент, предоставляемый системой.

Скриншот элемента множественного выбора в Chrome на Android, iPhone и iPad. На iPad и iPhone функция множественного выбора активна, и каждый из них предлагает уникальный интерфейс, оптимизированный под размер экрана.

Клавиатура и геймпад

Ниже представлена ​​демонстрация того, как использовать функцию <select multiple> с клавиатуры.

Этот встроенный множественный выбор не поддаётся стилизации и представлен только в компактном формате, не подходящем для отображения большого количества вариантов. Видите, как не видно всего многообразия вариантов в этом маленьком блоке? Хотя его размер можно изменить, он всё равно не так удобен, как боковая панель с флажками.

Разметка

Оба компонента будут содержаться в одном элементе <form> . Результаты этой формы, будь то флажки или множественный выбор, будут отслеживаться и использоваться для фильтрации таблицы, а также могут быть отправлены на сервер.

<form>

</form>

Компонент флажков

Группы флажков следует заключать в элемент <fieldset> и добавлять к ним тег <legend> . При такой структуре HTML программы чтения с экрана и FormData автоматически распознают взаимосвязь элементов.

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

После группировки добавьте элементы <label> и <input type="checkbox"> для каждого фильтра. Я решил заключить фильтр в <div> , чтобы CSS-свойство gap могло равномерно распределить их и сохранить выравнивание, когда надписи становятся многострочными.

<form>
  <fieldset>
    <legend>New</legend>
    <div>
      <input type="checkbox" id="last 30 days" name="new" value="last 30 days">
      <label for="last 30 days">Last 30 Days</label>
    </div>
    <div>
      <input type="checkbox" id="last 6 months" name="new" value="last 6 months">
      <label for="last 6 months">Last 6 Months</label>
    </div>
   </fieldset>
</form>

Скриншот с информативным наложением для элементов легенды и набора полей, показывает цвет и название элемента.

компонент <select multiple>

Редко используемая функция элемента <select> — это multiple . При использовании этого атрибута с элементом <select> пользователь может выбрать несколько вариантов из списка. Это похоже на изменение взаимодействия со списком радиокнопок на список флажков.

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

Чтобы маркировать и создавать группы внутри элемента <select> , используйте элемент <optgroup> и задайте ему атрибут label и значение. Этот элемент и значение атрибута аналогичны элементам <fieldset> и <legend> .

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

Теперь добавьте элементы <option> для фильтра.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      <option value="last 30 days">Last 30 Days</option>
      <option value="last 6 months">Last 6 Months</option>
    </optgroup>
  </select>
</form>

Скриншот отображения на рабочем столе элемента с возможностью множественного выбора.

Отслеживание ввода с помощью счетчиков для информирования вспомогательных технологий

В этом пользовательском интерфейсе используется метод ролей статуса для отслеживания и ведения учёта фильтров для программ чтения с экрана и других вспомогательных технологий. Видео на YouTube демонстрирует эту функцию. Интеграция начинается с HTML-кода и атрибута role="status" .

<div role="status" class="sr-only" id="applied-filters"></div>

Этот элемент будет озвучивать изменения, внесённые в содержимое. Мы можем обновлять содержимое с помощью CSS-счётчиков по мере того, как пользователи взаимодействуют с флажками. Для этого сначала нужно создать счётчик с именем в родительском элементе элементов inputs и state.

aside {
  counter-reset: filters;
}

По умолчанию счетчик будет равен 0 , что замечательно, так как в этой конструкции по умолчанию ничего не :checked .

Далее, чтобы увеличить наш новый счётчик, мы будем выбирать дочерние элементы элемента <aside> , отмеченные :checked . По мере того, как пользователь меняет состояние входных данных, счётчик filters будет увеличиваться.

aside :checked {
  counter-increment: filters;
}

CSS теперь учитывает общий счётчик чекбокса, а элемент роли статуса пуст и ожидает значений. Поскольку CSS хранит счётчик в памяти, функция counter() позволяет получить доступ к значению из содержимого псевдоэлемента :

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

HTML-код элемента роли статуса теперь будет сообщать экранному диктору «2 фильтра». Это хорошее начало, но мы можем сделать лучше, например, поделиться количеством обновлённых фильтрами результатов. Мы выполним эту работу с помощью JavaScript, так как это выходит за рамки возможностей счётчиков.

Скриншот программы экранного доступа MacOS, объявляющей количество активных фильтров.

Гнездовое волнение

Алгоритм счётчиков отлично показал себя с CSS-вложением (nesting-1) , так как мне удалось поместить всю логику в один блок. Он кажется портативным и централизованным для чтения и обновления.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

  & #applied-filters::before {
    content: counter(filters) " filters ";
  }
}

Макеты

В этом разделе описываются макеты двух компонентов. Большинство стилей макета предназначены для компонента «Флажок» рабочего стола.

Форма

Для оптимизации читаемости и удобства сканирования максимальная ширина формы ограничена 30 символами, что фактически задаёт оптическую ширину линии для каждой метки фильтра. Форма использует сетку и свойство gap для разделения наборов полей.

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

Элемент <select>

Список меток и флажки занимают слишком много места на мобильных устройствах. Поэтому макет проверяет основное указывающее устройство пользователя, чтобы адаптировать интерфейс к сенсорному экрану.

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

Значение coarse означает, что пользователь не сможет взаимодействовать с экраном с высокой точностью с помощью своего основного устройства ввода. На мобильном устройстве значение указателя часто coarse , поскольку основным способом взаимодействия является касание. На настольном устройстве значение указателя часто fine , поскольку обычно подключена мышь или другое высокоточное устройство ввода.

Полевые наборы

Стиль и макет по умолчанию для <fieldset> с <legend> уникальны:

Скриншот стилей по умолчанию для набора полей и легенды.

Обычно для задания интервалов между дочерними элементами я использую свойство gap , но уникальное расположение <legend> затрудняет создание равномерно распределенных дочерних элементов. Вместо gap используются селектор смежных элементов и margin-block-start .

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

Это позволяет пропустить корректировку пространства <legend> путем охвата только дочерних элементов <div> .

Скриншот, на котором показаны поля между входными данными, но не легенда.

Метка фильтра и флажок

Текст метки, являющийся прямым дочерним элементом <fieldset> и укладывающийся в пределы максимальной ширины формы 30ch , может переноситься, если слишком длинный. Перенос текста — это хорошо, но несовпадение текста с флажком — плохо. Flexbox идеально подходит для этого.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Скриншот, показывающий, как галочка выравнивается по первой строке текста при многострочном переносе.
Играйте больше в этом Codepen

Анимированная сетка

Анимация макета реализована с помощью Isotope — производительного и мощного плагина для интерактивной сортировки и фильтрации.

JavaScript

Помимо помощи в организации аккуратной анимированной, интерактивной сетки, JavaScript используется для сглаживания некоторых шероховатостей.

Нормализация пользовательского ввода

В этом проекте используется одна форма с двумя разными способами ввода данных, и они сериализуются по-разному. Однако с помощью JavaScript мы можем нормализовать данные.

Скриншот консоли JavaScript DevTools, на которой показаны результаты нормализованных данных.

Я решил выровнять структуру данных элемента <select> со структурой сгруппированных флажков. Для этого к элементу <select> добавляется прослушиватель событий input , после чего сопоставляются его selectedOptions .

document.querySelector('select').addEventListener('input', event => {
  // make selectedOptions iterable then reduce a new array object
  let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
    // parent optgroup label and option value are added to the reduce aggregator
    data.push([opt.parentElement.label.toLowerCase(), opt.value])
    return data
  }, [])
})

Теперь можно смело отправлять форму или, в случае этой демонстрации, указывать Isotope, по какому принципу следует фильтровать данные.

Завершение элемента роли статуса

Элемент подсчитывает и объявляет количество фильтров только на основе взаимодействия с флажками, но я посчитал, что было бы хорошей идеей дополнительно поделиться количеством результатов и убедиться, что выбор элемента <select> также подсчитывается.

<select> выбор элемента, отраженный в counter()

В разделе нормализации данных прослушиватель уже был создан для входных данных. В конце этой функции становится известно количество выбранных фильтров и количество результатов для этих фильтров. Эти значения можно передать элементу роли состояния следующим образом.

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

Результаты отражены в элементе role="status"

:checked предоставляет встроенный способ передачи количества выбранных фильтров элементу роли статуса, но не отображает количество отфильтрованных результатов. JavaScript может отслеживать взаимодействие с флажками и после фильтрации сетки добавлять textContent , как это делал элемент <select> .

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

В целом эта работа завершает объявление «2 фильтра дают 25 результатов».

Скриншот программы экранного доступа MacOS, объявляющей результаты.

Теперь наши прекрасные возможности вспомогательных технологий будут доступны всем пользователям, независимо от способа взаимодействия с ними.

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы поступили? 🙂

Давайте разнообразим наши подходы и изучим все способы разработки в интернете. Создайте демо, пришлите мне ссылку в Твиттер , и я добавлю её в раздел ремиксов сообщества ниже!

Ремиксы сообщества

Пока что здесь нечего смотреть!