Поиск
Три формы поиска и одна вещь, которую портят почти все — экран, показанный, когда ничего не нашлось.
Что решает
Поиск — не одно, а три. «Найди мне запись», «выбери из слишком многих вариантов» и «выполни команду, название которой я знаю» выглядят похоже и ведут себя по-разному: другой ввод, другие результаты, другой отказ.
Отказ — там, где разница перестаёт иметь значение: у каждой из трёх форм есть состояние «ничего не нашлось», и это наименее продуманный экран в большинстве продуктов.
Анатомия
Три формы, и условие для каждой записано в библиотеке:
- Вводится строка —
GrInput. Обычный поиск по странице или списку, где результаты появляются в другом месте, а поле только собирает запрос. - Вариантов слишком много для списка —
GrAutocomplete. Комбобокс: подбор по мере ввода, асинхронная загрузка, выбор в конце. Результат — значение, а не страница. - Команд много и они разбросаны —
GrCommandPalette. ⌘K по тому, что умеет приложение, — для того, кто знает название и не хочет искать кнопку.
Вокруг всех трёх — одни и те же четыре:
- фильтры —
GrChipGroupдля строки фильтров,GrSegmentedдля переключения вида одних и тех же результатов; - ожидание —
GrSkeleton, пока первые результаты в пути; - ничего не нашлось —
GrEmptyState, и это другой экран, чем «пусто с самого начала»; о нём ниже; - слишком много —
GrPagination.
Пример
Так и не так
Так — Сказать, что искали и сколько нашли
«7 результатов по запросу select» делает две работы: подтверждает, что запрос дошёл неискажённым, и задаёт ожидание до того, как глаз пошёл по списку. И то и другое важнее всего, когда число маленькое.
Так не надо — Пустой список как ответ «ничего не нашлось»
Пустая область под строкой поиска неотличима от сломанного поиска. Человек не может понять, это «нет совпадений», «ещё грузится» или «всё упало», — а реакции на все три разные.
Так — Предложить следующий шаг, когда не нашлось
«Ничего не нашлось» — вопрос, а не точка: покажите запрос, предложите снять
фильтр, дайте ближайшее. GrEmptyState существует ровно ради этого экрана, и
экран заслуживает той же заботы, что и результаты.
Так не надо — Один экран на «пусто» и «не нашлось»
«Здесь пока ничего нет — создайте первое» верно для нового аккаунта и неверно после поиска: ничего не создано не потому, что не создавали, а потому что не совпало, и кнопка предлагает не то. Два состояния — два текста.
Так — Отпускать фокус по Tab
В GrAutocomplete Tab закрывает панель и отдаёт фокус дальше по документу.
Человек, заполняющий форму, идёт по полям, и поиск — одно из них, а не место, где
его держат.
Так не надо — Искать на каждое нажатие без узды
Запрос на символ — это четыре запроса на «sele» и три ответа, приходящих не по порядку. Придержите ввод и отменяйте обогнанное, иначе список покажет ответ на запрос, который человек уже дописал.
Доступность
Комбобокс — паттерн с именем. GrAutocomplete его реализует: стрелки ходят
по вариантам, активный вариант объявляется активным, а не только подсвечивается,
и фокус остаётся в поле, пока по списку идут.
Число результатов надо объявить. Зрячий видит, что список появился; скринридеру нужно сказать — живая область с «7 результатов» и есть всё устройство, а без неё поиск нем.
Загрузка — состояние, а не украшение. Между последним нажатием и первым
результатом есть промежуток, когда ответ неизвестен, и он должен восприниматься:
GrSkeleton резервирует форму, а текст говорит, что происходит.
Портал меряет собственный поиск так же, как советует: доля запросов, не давших ничего, — отслеживаемое число, и оно питает контент-роадмап. Поиск, ничего не нашедший, — не поломка поиска, а страница, которой ещё нет.
Компоненты паттерна
Список строится из frontmatter, а не из ссылок в тексте: переименование слага здесь не теряется.
GrInputФормыGrAutocompleteФормыGrCommandPaletteНавигацияGrChipGroupДанныеGrSegmentedФормыGrEmptyStateОбратная связьGrSkeletonОбратная связьGrPaginationНавигация
Клавиатура
Контракты приходят из библиотеки. Паттерн их не пересказывает — он на них ссылается.
GrInputКонтракт компонентаGrAutocompleteКонтракт компонентаGrCommandPaletteКонтракт компонентаGrChipGroupКонтракт компонентаGrSegmentedКонтракт компонентаGrPaginationКонтракт компонента