Лічильник кількості
Мінус, число, плюс. Кількість товару в кошику і в рядку оформлення.
Крок 7.13: демо було саморобкою, а файл - порожнім
До цього кроку анатомія збиралася руками з .ctrl + .b + .cap - трьох класів, яких жоден лічильник у продукті ніколи не носив. .ctrl це обгортка тулбару лістингу, .cap - підпис тригера сортування, .b - лічильник фільтрів. А сам stepper.css тримав рівно два правила, і обидва існували, щоб ця вигадка виглядала правильно.
Через хибний якір неправильним був і весь блок «Де вживається»: він перелічував 13 екранів з .ctrl, тобто лістинги й пошук, де лічильника немає взагалі.
Що знайшлось під сподом
Лічильник існує, він на двох екранах, де рахуються гроші, і його було оголошено двічі:
| Клас | Файл | Входжень | Кнопка | Число |
|---|---|---|---|---|
.ci-qty | cart-row.css | 16, кошик | 32 × 34 | 30 |
.co-qty | checkout-form.css | 19, оформлення | 30 × 32 | 28 |
Розмітка однакова, рамка 1px однакова, радіус однаковий, ховер однаковий, моношрифт із tabular-nums однаковий. Три числа врізнобіч - і число 32 в обох, як ширина в одному й висота в другому. Ніхто такого не обирав; так виглядають два файли, що розійшлись.
Зведено на 32 × 34, рішення власника, редакція кошика. Лічильник оформлення виріс на 2px у кожен бік; більше на цих екранах не змінилось нічого. Виміряно: 22 стани сторінок, 17 046 елементів - зрушилось 144, усі на трьох сторінках чекауту, і в кошику жодного. Сірий прототип не зрушив теж.
tabular-nums, ширина стала 30 - кнопки не стрибають на переході з 9 на 10[disabled] - рамка зникає, чорнило стає --mark-disabled, курсор not-allowedПерші два демо живі - натисніть. Третє вимкнене й не рушить. Два перші відрізняються лише класом (.ci-qty і .co-qty) і мають бути неможливо різними: у цьому й полягав крок.
Це розмітка продукту, взята з cart.html і checkout.html як є. Знак у html, гліф на екрані - той самий обмін, що й у решти набору, і робить його system/marks.js.
А поведінку стенд мусив написати сам, і це знахідка
Стилі стенд бере з того самого system/index.css, що й крамниця, знак - з того самого system/marks.js. Поведінки лічильника в системі немає: крок живе у двох обробниках у wireframes/_nav.js, один прив'язаний до .cart-drawer, другий до кореня чекауту. Тому демо вище довелось оживляти окремим кодом на самій сторінці - за тим самим правилом (aria-label), але це друга копія. Компонент, чия поведінка належить сторінці, ніде не показується цілком.
Що зламав крок 7.11 і чим це полагоджено
Обробник вирішував напрямок так: /−|-/.test(step.textContent) - тобто читав знак усередині кнопки. Крок 7.11 віддав цей знак наборові, а заміна пише <svg> в кнопку, тож textContent став порожній, тест повернув false і кожне натискання падало в гілку плюса.
Виміряно до правки: у кольоровому кошику 1 → 2 → 3, у кольоровому чекауті так само. Сірий шар лишив свої символи і працював - саме тому ніхто й не спіймав. Тепер напрямок питається в aria-label, який стоїть на цих кнопках у обох шарах і якого малюнок стерти не може. Після правки всі шість пар екран×ширина дають 1 → 2 → 1 → 1.
Правило ширше за цей випадок: контрол, чия поведінка залежить від того, як його намальовано, це контрол, який чекає, коли зламатись.
Крок 7.14: число набирається, а не тільки клікається
Це був найдорожчий рядок у «Чого бракує»: щоб поставити 30, треба було натиснути плюс тридцять разів - а 30 це звичайне замовлення тренера (Job 1: закупівля на 5-30+ спортсменів). Тепер .qn це <input>.
Тільки цифри - літера, знак чи вставлене «2 шт» до суми не доходять. Від 1 до 99, і 99 це та сама стеля, яку плюс мав завжди: два входи, одне правило. inputmode="numeric" плюс pattern="[0-9]*" піднімають на телефоні цифрову клавіатуру, а не повну. Стрілки вгору й вниз роблять те саме, що дві клавіші обабіч. Enter знімає фокус.
Порожнє поле дозволене, поки в ньому пишуть, і затискається на виході. Людина, яка стерла 1, щоб набрати 12, не має бачити, як одиниця повертається їй під пальці.
type="text", а не type="number", і це рішення: числове поле малює власні стрілки-спінер - другу пару тих самих клавіш просто поруч, - пускає e, мінус і крапку, і міняє значення від коліщатка миші над ним.
Порядок правок був згори вниз, як велить wireframes/CLAUDE.md §6: спершу вузол ia/docs/pages/cart.md і його канонічна картинка ia/cart.html, тоді підпис поля в voice/docs/microcopy.md, тоді структура в сірому шарі, і аж тоді кольорова копія. Зміна роду контрола належить вузлу, а не вайрфрейму.
Матриця осей, а не перелік входжень: кожен рядок це питання, на яке відповідає розмітка, і правило, за яким обирають.
| Вісь | Значення | Правило вибору |
|---|---|---|
| Розмір | один: кнопка 32 × 34, число 30, рамка 97 × 37 | Варіанта немає і після зведення бути не повинно. «Компактний у шухляді», який тут стояв раніше, ніколи не існував у коді. |
| Клас | .ci-qty у кошику · .co-qty в оформленні | Два імені, одне правило. Перейменувати в одне не можна сьогодні: обидва живуть і в wireframes/, замороженому з етапу 05. Додати клас безпечно в обидва боки, перейменувати - ні, тож це пункт Кроку 6. |
| Стан | звичайний · [disabled] | Вимкнений приходить із наявності товару, а не з розмітки: див. cart-oos.html. |
У Рядок кошика і в Форма чекауту. Виміряно на живих екранах: 5 сторінок у design/, 15 контролів - cart.html і cart-oos.html тримають свої в шухляді кошика, checkout.html, checkout-loggedin.html і checkout-noaddr.html - у рядку замовлення. Job 1: коуч міняє кількості десятки разів за сесію, тому крок має бути дешевим.
Правило
Число моноширинне. Інакше кнопки їздять при переході з 9 на 10, і палець промахується.Антиправило
Не заміняй мінус на одиниці іконкою кошика мовчки. Сьогодні мінус просто впирається в 1, і це чесно. Якщо колись дія почне міняти зміст, вона мусить сказати про це підписом, а не тільки гліфом - див. Іконка.У коді є :hover (рамка яскравішає, клавіша бере підкладку й акцентне чорнило) і [disabled]. Ховер навмисне закритий через :not([disabled]), тож вимкнена клавіша не світиться під курсором.
15 клавіш на п'яти екранах, і до цього кроку мінус і плюс не робили при натиску нічого: уся відповідь була наведенням, на контролі, який існує, щоб по ньому багато разів тиснули пальцем. Цей файл сам пише, що звичайне замовлення тренера - 30 одиниць. Тридцять тапів без жодного підтвердження - це та версія контрола, що поїхала в продукт.
Під пальцем клавіша бере --bg-action-soft (#FFF9F5), на позначку тепліше за --bg-surface наведення, щоб на миші два стани все ще розрізнялись, коли лягають разом.
І кільце малював Chrome. Зміряно, не припущено: у спокої клавіша віддавала outline-style: none, під :focus-visible - rgb(0, 95, 204) auto 1px. Це той самий типовий контур, який 7.31 знайшов у чипа, а 7.32 у перемикача вигляду; цей прохід знайшов ще два - тут і на складеній дії. Кільце всередині, бо рамка ріже вміст через overflow: hidden, і зовнішнє кільце на клавіші зрізав би той самий короб, що її тримає. Кільце самої рамки на :focus-within - інша робота й лишається: воно каже «ви в цьому контролі», а це - «ви на цій клавіші».
-> #FFF9F5Кроковий лічильник носить три імені - .ci-qty у рядку кошика, .co-qty в оформленні і .cq у сесії тренера - і файл пише їх одним селектором. Ім'я в кожного своє, бо розмітку пише сірий шар, а він заморожений; обличчя одне, бо збільшити кількість це одна робота.
Сторінка звіряє себе з файлом: 3 власних класів і 2 станів оголошено в stepper.css. Нижче - ті, яких на цій сторінці немає ні в демо, ні в розмітці. Чужий контекст (його тут немає) не рахується: у цих селекторах компонент не є підметом, він лише стоїть усередині когось.
Робота: ВІДПОВІДЬ. Рухається: border-color рамки, background і color кнопок. Тривалість --dur-fast (150ms), крива --ease-standard. Значення читаються через var() з tokens.css: підміна токена в браузері веде за собою кожне з них, тобто числа у файлі немає. При prefers-reduced-motion рух зникає, а стан лишається.
Прочитані з stepper.css, не переписані руками. Колір іде лише через роль, геометрія - напряму з примітива.
| Семантичні ролі (9) | Примітиви (8) |
|---|---|
--bg-action-soft --bg-surface --line-action --line-control-hover --line-strong --mark-disabled --ring-focus --text-action --text-body | --font-mono --fs-14 --fs-16 --fw-semibold --radius-8 --size-30 --size-32 --size-34 |
| – | --dur-fast --ease-standard |
--size-32 додано на цьому ж кроці. 32 вже було в продукті двічі як літерал, у двох файлах, і означало різне: ширину клавіші в кошику й висоту в чекауті. Значення, яке повторюється, отримує ім\'я.
aria-label тут не оздоба: це єдине, з чого обробник дізнається напрямок кроку, відколи знак малює набір. У рядку оформлення та сама розмітка з class="co-qty" і type="button".
Якорі: .ci-qty .co-qty
5 екранів у design/, 15 контролів: cart.html, cart-oos.html (у шухляді), checkout.html, checkout-loggedin.html, checkout-noaddr.html (у рядку замовлення). У wireframes/ ті самі плюс cart-coach.html.
Чого бракує
- Клавіша 32 × 34 проти мінімуму дотику 44, який решта набору тримає. Число - рішення власника, і розрив реальний. Розширити зону дотику до 44 наосліп не можна: клавіші стоять за 30px одна від одної, тож зони перекрились би і натиск біля середини влучав би не туди. Це рішення, а не правка
- Немає межі складу - плюс упирається в 99, бо залишку прототип не знає, а
[disabled]ставить лише розмітка недоступного товару - Два імені на один контрол -
.ci-qtyі.co-qty. Правило вже одне, розмітка ще ні; перейменування чекає Кроку 6, бо обидва класи є і в замороженому сірому шарі
Не домальовано наперед і не замовчано: етап 09 стартує з цього списку, зведеного в design/kit/docs/architecture.md.