Іконка
Обгортка навколо inline-SVG. Не гліф, а коробка, у якій гліф стоїть: скільки місця займає, як тримає рядок, звідки бере колір і розмір.
Які гліфи існують, як вони намальовані, чому штрих саме такий і чому брендові марки навмисно не з набору - це Основи → Набір іконок. Тут - icon.css, тобто оправа. До кроку 7.16 обидві сторінки називались «Іконки», і чотири правила стояли на них двічі; тепер кожне має одного власника і посилання на другого.
Крок 7.16: сторінка применшувала себе вдесятеро
Тут стояло «34 рядки, 4 екрани». Виміряно на живих сторінках: .uiv-ic є на 39 екранах із 39 - на кожному - і це 5 291 входження, найчастіший клас усієї системи. Плюс .uiv-brand 62 на 31 екрані і .uiv-ic.uiv-trail 1 562. «4 екрани» рахувались по wireframes/ і по двох якорях із чотирьох.
І два правила з чотирьох, які файл оголошує, ця сторінка не показувала взагалі: мітка в кінці підпису й математика брендового чорнила. Обидва нижче.
Це демо до 7.91 малювало порожнечу, і причина в самому атомі
Тут стояли три текстові гліфи -★, ●, ▾ - усередині .uiv-ic. Заміряно: ширина в них була (22, 20, 10), а висота нуль, тож демо займало 50px відступів і не показувало нічого. Так і має бути: .uiv-ic це display: flex з line-height: 0, і свій розмір коробка бере від SVG усередині. Текстовий знак їй розміру не дає - а перший рядок цієї ж сторінки каже «обгортка навколо inline-SVG». Демо суперечило ліду. Тепер тут data-glyph, які заповнює icons.js - той самий шлях, яким гліф приходить на екран крамниці. Знайшов це не погляд, а прохід по всіх стендах кіта на 390 і 1280, заведений на 7.91..chev - шеврон, який обертається, коли рядок відкривається. Це єдиний клас файлу, що має власну анімацію, і живе він у коробці, а не в гліфі: marks.js міняє знак усередині, а поворот лишається на коробці - інакше заміна забрала б його з собою. 16 входжень.
.uiv-ic - inline-flex і line-height:0 - інакше гліф зсуває рядокem, тому іконка росте разом з текстомcurrentColor - іконка не вирішує самаМатриця осей, а не перелік входжень: кожен рядок це питання, на яке відповідає розмітка, і правило, за яким обирають.
| Вісь | Значення | Правило вибору |
|---|---|---|
| Розмір | 1.05em базовий · фіксований у px там, де поруч немає тексту | В рядку - em, щоб іконка масштабувалась із підписом. Окремо - px, бо успадковувати нема від чого. |
| Колір | успадкований | Іконка ніколи не оголошує колір сама. Хочеш іншу - зміни роль тексту, у якому вона стоїть. |
| Мітка в кінці | .uiv-trail - 1.15em плюс --space-4 зліва | Стрілка після підпису це пунктуація речення, а не іменник: вона росте з підписом, а не зі шкалою кнопки. 1 562 входження. |
| Брендова марка | .uiv-brand - коробка --brand-ink, малюнок переростає її | Кожна марка заповнює свій viewBox по-різному, тож рівна коробка дає нерівне чорнило. 62 входження. |
Товщина штриха тут не оголошується. Вона задана в самих шляхах гліфів, тобто належить наборові - див. Основи → Набір іконок, розділ «Розмір, товщина, колір».
Пунктуація, а не іменник. Мітка перед підписом називає те, на що контрол діє, тож бере шкалу кнопки (18 / 20 / 24). Мітка після каже, куди веде натиск - вона належить реченню й росте з підписом: 1.15em, на волосину вище за літери поруч.
Чому це окремий клас, а не :last-child
Це єдине місце, де правило кроку 6.9 «розмітка вже каже, з якого боку» не тримає. :last-child рахує елементні діти, а підпис це голий текстовий вузол - тож у <a><span class="uiv-ic">…</span>Квіз</a> ведуча мітка теж остання елементна дитина, і правило спрацьовує на ній. Виміряно: 34 посилання навігації втратили свою мітку з 18 до 16.1 з першої спроби. CSS текстового вузла не бачить, тож бік називає той, хто мітку ставить - uivTrailMark() і menu.js.
І свій пробіл вона приносить сама. Знак ставиться з прибраним пробілом перед ним, бо кнопка це flex-рядок, чий gap уже відсуває мітку, а лишений пробіл відносив її вдвічі далі за решту набору. Посилання flex-рядком не є й не дає нічого, тож мітка ставала впритул до останньої літери - виміряно на голові мега-меню. Пробіл належить мітці там, де його не дає ніхто інший.
Цей файл вантажиться після button.css, тож за рівної специфічності саме цей рядок б'є шкалу кнопки. Порядок тут - правило, а не випадковість.
Око було праве, а числа виглядали невинно: Apple стояв 18px проти Google 19px - п'ять відсотків різниці в коробці - і читався як дві третини розміру. Причина в тому, що кожна марка заповнює свій viewBox по-своєму, тож рівна коробка дає нерівне чорнило:
| Марка | Заповнює viewBox | Що це |
|---|---|---|
| telegram · viber | 100% | значок: диск і є марка |
| 83% | гліф із повітрям навколо | |
| apple | 72% | силует, повітря ще більше |
Тож компонент оголошує чорнило, якого хоче, а кожна марка виводить свою коробку з частки, яку заповнює. Множники 24/20 і 24/17.3 - виміряні на шляхах, а не вгадані.
Крок 7.3: коробка фіксована, переростає тільки малюнок
Оскільки кожна марка роздуває свій svg заради рівного чорнила, той svg став ще й найвищим усередині кнопки - тобто задавав її висоту. Виміряно на auth.html: «Продовжити з Google» 55.8 і «Продовжити з Apple» 59.6, два однакові контроли один під одним, 3.8px нарізно, обидва вище за сходинку M у 52. Марка може вирішувати, скільки їй чорнила; вона не може вирішувати, якої висоти контрол. Тож обгортка прибита до --brand-ink, а більший малюнок з неї виливається.
Крок 7.16: і все це вище було неробочим від самого початку
Виміряно на auth.html: --brand-ink: 20px, і всі чотири марки намальовані рівно на 20 - тобто саме та рівна коробка, задля уникнення якої правило й писалось. Google просив calc(20px × 1.2) = 24 і отримував 20.
Причина в одному слові, якого не було. .uiv-brand це inline-flex, прибитий до --brand-ink, отже svg усередині це flex-елемент, а flex-елемент за замовчуванням стискається. 24 не влазить у рядок 20 - і стискається до 20. overflow:visible давав малюнкові дозвіл бути намальованим поза коробкою; він ніколи не давав дозволу бути більшим. Це робить flex:none.
Після правки: Google 24, Apple 27.8 при коробці 20, а висота кнопки - те єдине, що крок 7.3 боронив, - лишилась 52. На чекауті 21.6 і 25 при коробці 18, висота 40. Контрфактично: повернув flex-shrink назад - усі знову по 20, тобто рухається саме те, про що правило.
І другий дефект, який ця правка витягла на світло. Месенджери в підвалі носять обидва класи, uiv-ic uiv-brand, і footer.css задавав їм svg{ width:16px } - тобто ліз повз компонент до малюнка. Коробка при цьому лишалась 1.05em = 14.7, і розбіжність ховав рівно той самий flex-shrink. Контракт написаний у button.css: той, хто кличе, каже, скільки чорнила; суму робить icon.css. Тепер підвал каже --brand-ink: 16px, коробка й малюнок обидва 16, рядок не зрушив.
Іконка стоїть поруч зі словом, а не замість нього, всюди крім місць, де підпис уже є в aria-label і місця фізично немає (кнопка «у кошик» на картці). Принцип 5 - «звичайні слова в підписах»; піктограма без слова це загадка.
Правило
Іконка успадковує колір. Один клас на всі 40 гліфів, колір приходить від ролі тексту.Антиправило
Не фарбуй іконку окремо..uiv-ic{color:...} у файлі компонента розриває зв'язок з підписом: підпис змінить роль, а іконка лишиться старою. Так само не роби іконку-без-підпису там, де підпис влазить - див. Кнопка.У коді немає жодного стану, крім базового.
Сторінка звіряє себе з файлом: 4 власних класів і 0 станів оголошено в icon.css. Нижче - ті, яких на цій сторінці немає ні в демо, ні в розмітці. Чужого контексту тут більше немає: .uiv-sort, який цей рядок називав чужим, крок 7.16 видалив - клас не зустрічається в жодному html і в жодному скрипті з кроку 7.2.
Робота: ВІДПОВІДЬ. Рухається: transform шеврона, 180 градусів. Тривалість --dur-fast (150ms), крива --ease-standard. Значення читаються через var() з tokens.css: підміна токена в браузері веде за собою кожне з них, тобто числа у файлі немає. При prefers-reduced-motion рух зникає, а стан лишається.
Стан, який його повертає, живе НЕ тут: .ord.open в order-row.css і .trustsec[open] в trust-strip.css. Атом несе свій перехід, батько перемикає стан - тому перепис станів, який читає файли поодинці, спершу назвав цю іконку рухом без стану.
Прочитані з icon.css, не переписані руками. Колір іде лише через роль, геометрія - напряму з примітива.
| Семантичні ролі (0) | Примітиви (2) |
|---|---|
- | --lh-flat --space-4 |
| – | --dur-fast --ease-standard |
Жодної семантичної ролі, і це не пропуск. Обгортка кольору не оголошує взагалі - вона бере currentColor, тобто роль тексту, у якому стоїть. Компонент без жодної колірної ролі темою перемикається сам собою, бо перемикається текст навколо нього.
--brand-ink у списку немає навмисно: це не токен, а власна змінна компонента, оголошена тут же в .uiv-brand. Її значення читають чотири правила марок, і жодне з них не читає її ззовні.
Розмітку майже ніде не пишуть руками: system/marks.js ставить обгортку сам, коли міняє знак на гліф, а data-brand заповнює uivBrandSvg(). Саме тому «де вживається» рахується в браузері, а не пошуком по файлах - у файлах цих 5 291 обгорток немає.
Якорі: .uiv-ic .uiv-trail .uiv-brand .chev
Виміряно на живих сторінках design/, а не порахувано в розмітці - більшість цих обгорток ставить marks.js уже в браузері:
| Клас | Екранів | Входжень |
|---|---|---|
.uiv-ic | 39 із 39 | 5 291 |
.uiv-ic.uiv-trail | - | 1 562 |
.uiv-brand | 31 | 62 |
.chev | - | 16 |
Чого бракує
- Немає розмірної шкали -
1.05emце одне значення, а не сходинки. Для іконки без підпису успадковувати нема від чого, і там сьогодні фіксовані px нарізно в кожному компоненті - Стану немає жодного - ані ховера, ані вимкненого. Це не пропуск: обгортка кольору не має, тож усі її стани це стани тексту поруч. Але щойно з'явиться перша іконка-контрол без підпису, стан доведеться вирішувати, а не домальовувати
- Знак поза контролом набір не малює -
marks.jsходить по контролах, тож ⚠ у банері помилки OTP лишається шрифтовим на стенді, хоча в крамниці його міняєdesign/_nav.js. Третій випадок того самого шва після серця й кошика
Не домальовано наперед і не замовчано: етап 09 стартує з цього списку, зведеного в design/kit/docs/architecture.md.