Компонент · рівень 1

Меню вибору

Одне значення зі списку. Крок 7.2. До нього продукт мав два способи вибрати одне зі списку, і жоден не був цілим: сортування - список, зібраний руками, який відповідав лише миші, а на телефоні не відкривався взагалі; вибір клієнта - нативний <select>, у якого все це є безкоштовно, але розкритий список малює операційна система. Тепер обидва - один атом.

рівень 1design/system/components/menu.cssdesign/system/menu.js2 вживання
Два джерела, один атом
з полязначення йде у форму
uivMenuFromSelect(select) · 44, драбина поля
з кнопкиміняє вигляд, а не значення
uivMenu(trigger, items) · 40, драбина кнопки
Select лишається значенням. Розмітка пікера - це й далі <select>: меню будується з нього і пише назад у нього, тому форма різниці не бачить, а сторінка без JS працює. Замінено закритий тригер і розкритий список, а не сенс контрола. Дія - не значення: «+ Новий клієнт…» виходить зі списку значень і стає кнопкою під роздільником. Саме через це пікер не міг лишитись нативним селектом: вибір такої опції означав би «значення = Новий клієнт», що неправда.
Клавіатура

Усе, що нативний <select> давав задарма і що тепер винні ми. У списку, зібраному руками до кроку 7.2, не було жодного рядка з цієї таблиці.

КлавішаНа тригеріУ списку
↓ / ↑відкриває - на обраному рядку, або на останньому для ↑рухає на рядок
Enter / Spaceвідкриваєобирає й закриває
Home / End-перший / останній
літери-стрибок на рядок, що з них починається (вікно 700 мс)
Esc-закриває, фокус повертається на тригер
Tabдалі по сторінцізакриває і йде далі

Ролі: тригер несе aria-haspopup="listbox" і aria-expanded, список - role="listbox", рядок - role="option" і aria-selected. Дія стоїть поза списком і тому не має ні ролі опції, ні галочки.

На телефоні - шторка

Нижче 480 список, прив'язаний до тригера, або тікає за екран, або лягає під палець. У продукті вже є нижня шторка для фільтрів, тож меню бере ту саму форму, а не вигадує другу: смужка-хват, рядки 52, ґрунт під усім екраном.

І шторка залишає свій тригер. .mtoolbar - липкий блок із z-index: 20, тобто власний контекст накладання: всередині нього жоден наш z-index не підніметься над нижньою панеллю (z 50), і шторка відкривалась під нею. Тому нижче 480 menu.js переносить список і ґрунт у <body>, поки меню відкрите, і повертає назад, коли закрите. Через це стан відкритості висить на самому списку (.menu-pop.open), а не на .menu.open .menu-pop.

ПОВЕДІНКА НА ШИРИНІ - етап 10, крок 4

Від якого порогу: --bp-grid-2col (620), було 479. Що саме міняється: відкрита випадайка стає повноекранним шитом зі скримом. Це поведінка, а не розмір, тож вона лишається точкою, а не стає плинною; питання було лише в тому, якою з двох, і продукт має дві. Нижче 620 списку немає де бути випадайкою - з тієї ж причини, з якої там ряд карток перестає бути однією колонкою. Наслідок вголос: між 480 і 619 меню тепер бере форму шита, яку раніше брало лише нижче 480. На 360 не зрушило нічого.
Правило і антиправило

Правило

Меню обирає значення, кнопка робить дію. Список - це listbox зі значень; усе, що не є значенням, стоїть під роздільником окремою кнопкою. Обраний рядок несе акцент і галочку: колір сам по собі не працює для того, хто його не бачить.

Одне меню за раз. Відкриття закриває будь-яке інше: два розкриті списки - це дві відповіді на одне питання.

Антиправило

Не роби меню з того, що веде. Мова, кабінет, мега-меню - це навігація: там вибір нічого не обирає, а переносить. Їхній список не listbox, і галочки в ньому бути не може.

Не лишай нативний select там, де в списку є дія. Це єдина причина, з якої пікер клієнта перестав бути селектом - не вигляд.
Рух

Робота: ВІДПОВІДЬ. Рухається: шеврон тригера (transform), фон і колір рядка списку. Тривалість --dur-fast (150ms), крива --ease-standard. Значення читаються через var() з tokens.css: підміна токена в браузері веде за собою кожне з них, тобто числа у файлі немає. При prefers-reduced-motion рух зникає, а стан лишається.

Живе меню з міткою будує menu.js вище на цій сторінці: тут навмисно стоїть сам тригер, бо мітку тригера пише продукт, а не розмітка стенда.

Крок 4, дві поверхні. Поповер проявляється і не подорожує - він стоїть проти контрола, який його відкрив. Галочка обраного рядка - найменша поверхня продукту й єдина чиста ВІДПОВІДЬ серед двадцяти: --dur-fast, крива рядка, і приходить у ту саму мить, що й зміна кольору двома правилами вище. Без руху: галочка, яка їде, десь була, а вона не була.

Токени

Прочитані з menu.css, не переписані руками. Одинадцять ролей на шістнадцять примітивів, і в цьому наборі є одна пара, якої немає більше ніде: дві висоти тіні в одному компоненті.

Семантичні ролі (11)Примітиви (16)
--bg-page --bg-sunken --bg-rule--fs-14 --fs-16
--elevation-3 --elevation-4--fw-medium --fw-semibold --lh-snug
--scrim-overlay--radius-8 --radius-12 --radius-16 --radius-pill
--line-strong --ring-focus-control--size-20 --size-44 --size-52
--text-action --text-body --text-muted--space-4 --space-8 --space-12 --space-24
–--dur-base --dur-fast --ease-enter --ease-exit --ease-standard

--elevation-3 на десктопі і --elevation-4 на телефоні - це не непослідовність, це та сама відстань до різного тла. Спадне меню лягає на сторінку і мусить читатись відірваним від неї на один крок. Шторка на телефоні лягає на --scrim-overlay, тобто на затемнену сторінку, і крок від затемненого тла коштує дорожче - інакше вона читалась би приклеєною до скрима.

Чотири радіуси в одному файлі - 8 для рядка, 12 для коробки, 16 для шторки, pill для ручки, - і всі чотири це щаблі драбини. Жодного літерала геометрії у файлі немає взагалі, що для компонента з двома в'юпортними формами трапляється нечасто.