GrChip
Берут, когда снимаемая метка у сущности.
Обзор
Когда брать
- снимаемая метка у сущности — теги задачи, получатели письма, выбранные файлы: крестик у каждой, список ведёт приложение;
- фильтр, который включают и выключают —
selectableдаёт переключатель сaria-pressed, набор таких фильтров собираетGrChipGroup; - быстрый выбор из коротких вариантов — период, статус, приоритет: чипы занимают строку, а не выпадающий список;
- свой ряд тегов рядом с полем — когда ввод и хранение уже написаны, а нужна только плитка с крестиком.
Когда взять другое
| Нужно | Берите |
|---|---|
| Статус или ярлык без интерактива | GrBadge |
| Поле, которое само набирает теги из ввода | GrInputTag |
| Счётчик поверх кнопки или иконки | GrBadgeWrap |
| Один выбор из вариантов одной строкой | GrSegmented |
| Много вариантов, которые ищут вводом | GrSelect / GrAutocomplete |
Варианты все тона и виды — настоящим компонентом
tone \ dark | false | true |
|---|---|---|
primary | ||
neutral | ||
success | ||
warning | ||
danger | ||
info | ||
slate | ||
azure |
Размеры
xssmmdlg
Состояния
обычнаяdisabled
Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrChip label="Design" />Примеры 5 у каждого — живое демо и его код
Метки, которые снимают одним движением
closableдобавляет настоящую кнопку с именем метки — «Убрать vat», а не «Убрать» пять раз подряд.remove— просьба, а не факт: чип не прячет себя сам, список живёт на экране. Бейдж сообщает состояние; чип — то, чем объект пометили, поэтому его можно снять.Export fails for invoices with reverse-charge VAT#1824 · opened by Grace Wu 2 days agoLabelsbuginvoicesvatcustomer-reportedПолучатели и кто снаружи
Слот
#iconотличает коллегу от внешнего адреса, а тон это подкрепляет. Когда одной подписи мало, чтобы понять, что снимает крестик,remove-labelзадаёт имя целиком: «Remove Grace Wu from recipients».Send invoice INV-0418 ToAda LinGrace WuNorthwind AP1 recipient is outside Acme — the invoice leaves the workspace.
Фильтры, которые включают и выключают
selectableпревращает чип в переключатель сaria-pressed, аv-model:selectedдержит его состояние. Выбранный чип берёт плотную заливку своего тона — находится одним взглядом и отличается насыщением даже в монохроме. Чип из одного значка называетaria-label;disabledоставляет недоступный фильтр на виду.3 of 24 invoices · €4,035 due
Токены запроса в строке поиска
Токен ближе к коду, чем к тегу:
radius="semi"даёт ему форму ярлыка ключа,size="sm"вписывает его в поле. В слоте ключ и значение двух весов, иlabelне сказал бы, что снимается, — это делаетremove-label.status: overduecustomer: Northwindamount: > €1,0003 filters · 9 invoices
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
disabled | boolean | undefined | false | Чип недоступен: не выбирается и не снимается, крестик скрыт. Внутри группы — и от её `disabled`. |
tone |
| undefined | Тон чипа — те же цвета, что у `GrBadge`, чтобы в одном ряду они не расходились. Не задан — из группы и `GrConfigProvider`, иначе `neutral`. |
closable | boolean | undefined | false | Крестик. Внутри виджета он не кнопка — см. блок про роли ниже. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Ступень контрольной шкалы: цель нажатия той же высоты, что у соседних полей. Не задан — из группы и `GrConfigProvider`, иначе `md`. |
ariaLabel | string | undefined | undefined | Доступное имя, когда подпись не объясняет назначения. |
value | GrChipValue | undefined | undefined | Значение чипа в группе. Вне `GrChipGroup` не используется. |
dark | boolean | undefined | undefined | Заливка тоном вместо мягкой подложки — для чипа, который обязан выделяться. По умолчанию `false`. |
radius | GrBadgeRadius | undefined | undefined | Форма: `round` — таблетка, `semi` — ярлык, `square` — штамп. Не задан — из группы и `GrConfigProvider`, иначе `round`. |
label | string | undefined | undefined | Подпись. Нужна отдельно от слота: из неё собирается имя кнопки снятия — «Убрать» на двадцати кнопках подряд не даёт выбрать нужную. |
selectable | boolean | undefined | false | Чип становится переключателем: `aria-pressed`, `Enter`/`Space`. |
selectedv-model | boolean | undefined | false | Состояние переключателя вне группы. Внутри группы значение ведёт группа. |
removeLabel | string | undefined | undefined | Имя кнопки снятия целиком. Перебивает собранное из `label`. |
removeTabindex | number | undefined | undefined | `tabindex` кнопки снятия — для чипа внутри поля ввода. Там таб-стоп принадлежит `<input>`, а ряд чипов ходит стрелками: у `GrInputTag` кольцо оставляет ровно один `0`, у `GrAutocomplete` их нет вовсе. Проп нужен потому, что кольцом управляет родитель — он знает и порядок чипов, и что стоит за краем ряда. |
Слоты
| Slot | Type | Описание |
|---|---|---|
default | any | Подпись чипа. Внутри выбираемого чипа — только фразовое содержимое. |
icon | any | Иконка перед подписью. |
События
| Event | Сигнатура | Описание |
|---|---|---|
remove | (event: "remove"): void | — |
update:selected | (event: "update:selected", value: boolean): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
focus | () => void | undefined | Фокус на чип из кода. Действует на интерактивный чип: простой чип — `span` без таб-стопа. |
blur | () => void | undefined | Снять фокус с чипа. |
removeEl | HTMLButtonElement | null | Кнопка снятия — цель roving-фокуса у родителя. Отдаётся элементом, а не методом `focusRemove`: кольцу нужен сам узел, оно само решает, когда и куда переносить фокус. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrChipConfigurableProps, GrChipValue, GrChipSelection } from '@feugene/granularity/components/GrChip'Файлdist/types/components/GrChip/config.d.ts
const grChipConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChip">;Файлdist/types/components/GrChip/defaults.d.ts
Пропы GrChip, настраиваемые глобально через componentDefaults.
Только оформление: выбор и снятие относятся к экземпляру.
interface GrChipConfigurableProps {
tone: GrChipTone;
size: GrChipSize;
radius: GrChipRadius;
dark: boolean;
}Файлdist/types/components/shared/chipGroupContext.d.ts
Значение чипа в группе.
Не только строка: набор фильтров чаще всего адресуется id. Объекты сюда
намеренно не входят — значение уходит в data-value и в скрытое поле
нативной формы, а значит обязано иметь однозначное строковое представление.
type GrChipValue = string | number | boolean;Как группа обращается со множественностью выбора.
type GrChipSelection = 'single' | 'multiple';type GrChipGroupContext = {
selection: ComputedRef<GrChipSelection>;
isSelected: (value: GrChipValue) => boolean;Файлdist/types/components/GrChip/grChipStyles.d.ts
type GrChipTone = GrBadgeTone;type GrChipSize = GrComponentSize;type GrChipRadius = GrBadgeRadius;Файлdist/types/components/GrChip/safelist.d.ts
const grChipSafelist: string[];Файлdist/types/components/GrChip/GrChip.vue.d.ts
Интерактивный тег: снимаемый, выбираемый, с иконкой.
От GrBadge отличается не видом, а природой: бейдж — метка, чип — виджет.
Поэтому цвета и радиусы взяты у бейджа как есть (в одном ряду они обязаны
совпадать), а роль, клавиатура и размеры — свои.
interface GrChipProps {
/** Значение чипа в группе. Вне `GrChipGroup` не используется. */
value?: GrChipValue;
/**
* Подпись. Нужна отдельно от слота: из неё собирается имя кнопки снятия —
* «Убрать» на двадцати кнопках подряд не даёт выбрать нужную.
*/
label?: string;
/**
* Тон чипа — те же цвета, что у `GrBadge`, чтобы в одном ряду они не расходились.
* Не задан — из группы и `GrConfigProvider`, иначе `neutral`.
*/
tone?: GrChipTone;
/** Заливка тоном вместо мягкой подложки — для чипа, который обязан выделяться. По умолчанию `false`. */
dark?: boolean;
/**
* Ступень контрольной шкалы: цель нажатия той же высоты, что у соседних полей.
* Не задан — из группы и `GrConfigProvider`, иначе `md`.
*/
size?: GrChipSize;
/**
* Форма: `round` — таблетка, `semi` — ярлык, `square` — штамп.
* Не задан — из группы и `GrConfigProvider`, иначе `round`.
*/
radius?: GrChipRadius;
/** Чип становится переключателем: `aria-pressed`, `Enter`/`Space`. */
selectable?: boolean;
/** Состояние переключателя вне группы. Внутри группы значение ведёт группа. */
selected?: boolean;
/** Крестик. Внутри виджета он не кнопка — см. блок про роли ниже. */
closable?: boolean;
/** Чип недоступен: не выбирается и не снимается, крестик скрыт. Внутри группы — и от её `disabled`. */
disabled?: boolean;
/** Доступное имя, когда подпись не объясняет назначения. */
ariaLabel?: string;
/** Имя кнопки снятия целиком. Перебивает собранное из `label`. */
removeLabel?: string;
/**
* `tabindex` кнопки снятия — для чипа внутри поля ввода.
*
* Там таб-стоп принадлежит `<input>`, а ряд чипов ходит стрелками: у
* `GrInputTag` кольцо оставляет ровно один `0`, у `GrAutocomplete` их нет
* вовсе. Проп нужен потому, что кольцом управляет родитель — он знает и
* порядок чипов, и что стоит за краем ряда.
*/
removeTabindex?: number;
}interface GrChipEmits {
/** Переключатель вне группы сменил состояние (`v-model:selected`); в группе выбор ведёт она. */
(e: 'update:selected', value: boolean): void;
/** Чип просят снять — крестиком или `Delete`/`Backspace`. Удалить его из списка — забота родителя. */
(e: 'remove'): void;
}Файлdist/types/components/GrChip/index.d.ts
type GrChipInstance = ComponentExposed<typeof GrChipComponent>;Доступность
Паттерн APG
toggle button / option
| Клавиша | Действие |
|---|---|
| — | Не выбираемый чип клавиш не имеет: интерактив в нём один — крестик, и он обычная кнопка со своей остановкой Tab. Выбираемый чип — кнопка: Enter/Space переключают |
| — | Delete/Backspace снимают (если closable). Крестик у него в таб-порядке **не стоит**: внутри роли-виджета вложенная кнопка запрещена, и способ снять объявлен через aria-keyshortcuts |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Крестик — не всегда кнопка
Разметка чипа зависит от того, кликают ли по нему самому.
Чип не виджет (просто снимаемая метка) — корень <span>, а крестик
настоящая <button> со своим именем и своей остановкой Tab. Так устроены
чипы внутри GrInputTag.
Чип виджет (selectable или внутри группы) — корень <button>, и крестик
внутри него кнопкой быть не может: роль объявляет потомков презентационными
(axe ловит это как nested-interactive), а <button> внутри <button>
невалиден по контент-модели HTML. Поэтому крестик становится aria-hidden-
<span>, снятие уходит на Delete/Backspace, и чип объявляет это через
aria-keyshortcuts. Тем же приёмом живут закрываемые вкладки
GrTabs.
Практическое следствие: внутрь выбираемого чипа нельзя класть ссылку. Нужна ссылка — чип не выбираемый.
Снятие чип не выполняет
remove — это просьба, а не факт: состав набора лежит у потребителя, и убрать
элемент из массива может только он. Чип не прячет себя сам и ничего не
объявляет в живой регион — объявлять «убрано» до того, как это случилось, было
бы враньём.
Подпись нужна отдельно от слота
Проп label дублирует содержимое слота не по недосмотру: из него собирается имя
кнопки снятия. Двадцать кнопок с именем «Убрать» подряд не дают выбрать нужную —
имя обязано называть сам тег, поэтому по умолчанию оно «Убрать {label}».
Содержимое сложнее строки — задавайте removeLabel руками.
Выбор виден заливкой, а не контуром
Выбранный чип берёт плотный вариант своего тона — тот же, что даёт dark.
Оттенок при этом не меняется: набор фильтров, где выбранное перекрашивается в
соседний цвет, читается как радуга, а не как состояние.
Контур и лёгкое утолщение шрифта эту работу не тянут: в ряду из пяти фильтров выбранный по ним не находится с одного взгляда. Перепад светлоты находится — и переживает монохром и дальтонизм, в отличие от «стал чуть темнее».
Нецветовой канал при этом остаётся: у выбранного чипа font-weight: 600. Он же
вытягивает случай, когда всему набору задан dark и заливка у выбранного и
невыбранного совпадает.
Тона общие с бейджем, размеры свои
Палитра из восьми тонов и три радиуса взяты у GrBadge без
изменений: чип и бейдж часто стоят в одном ряду и обязаны совпадать по цвету.
А вот шкала размеров у чипа другая. У бейджа кегль на ступень мельче
(--gr-control-text-2xs…md), потому что метка не цель нажатия; по чипу кликают,
и кегль у него совпадает с соседним контролом (--gr-control-text-xs…lg).
Отсюда же явная высота, которой у бейджа нет вовсе.
Высота при этом ниже контрольной — 24 / 28 / 32 / 36 px против 28 / 32 / 40 /
44 у поля ввода и кнопки: чип плотнее, потому что ходит наборами и по несколько
штук в строке. Практическое следствие стоит знать заранее: потолок шкалы — 36px,
то есть до 44×44, которых требуют WCAG 2.5.5 и Apple HIG для цели под палец, чип
не дотягивается ни на какой ступени. Нужна именно такая цель — берите
GrButton с size="lg", а не чип.
Границы
- виртуализации нет. Тысяча чипов в ряду — это не ряд чипов, а список:
режьте набор сами или берите
GrSelectсvirtual; - перетаскиванием чипы не переставляются. Порядок задаёт массив
потребителя; нужен ручной порядок —
GrSortableList; - своего ввода у чипа нет. Он показывает готовое значение;
набор из ввода — это
GrInputTag.
Пропы этого примера