GrChipGroup
Берут, когда фильтры списка строкой.
Когда брать
- фильтры списка строкой — статусы, категории, метки: видно все варианты сразу, выбранных может быть несколько;
- выбор из коротких вариантов вместо списка — период отчёта, приоритет: вариантов до десятка, и прятать их под кнопку незачем;
- набор меток записи, который правят на месте —
closableдаёт крестик каждому чипу, аDeleteснимает тот, что под фокусом; - поле формы, где значение — набор — группа читает контекст
GrFormFieldи отдаёт значение в нативную форму поname.
Когда взять другое
| Нужно | Берите |
|---|---|
| Один выбор, вариантов немного, вид переключателя | GrSegmented |
| Один выбор с описаниями у вариантов | GrRadioGroup |
| Множественный выбор списком с отметками | GrCheckboxGroup |
| Вариантов десятки, их ищут вводом | GrSelect / GrAutocomplete |
| Ряд снимаемых меток без выбора | GrChip без группы |
Роль зависит от множественности выбора
selection="multiple" (по умолчанию) объявляет группу listbox с
aria-multiselectable, а чипы — option с aria-selected.
selection="single" — radiogroup и radio с aria-checked, как у
GrSegmented.
Роль чипы получают из контекста и сами её не выбирают: иначе набор из чипов с разными ролями объявлялся бы скринридеру как что угодно, кроме одного виджета.
Одна остановка `Tab`, внутрь — стрелками
Группа составная, поэтому занимает одну остановку в таб-порядке: активный чип
держит tabindex="0", остальные -1. Стрелки работают по обеим осям — чипы
переносятся на новую строку, и «вниз» означает следующий чип так же, как
«вправо». Home/End уводят к краям.
Стрелка двигает только фокус, даже в одиночном режиме. У чипов есть второе
действие — снятие по Delete, — и переносить выбор вместе с фокусом значило бы
менять модель при попытке дойти до нужного чипа. В radiogroup формы принято
обратное, но там у элементов одного действия.
Повторный выбор снимает отметку
В одиночном режиме клик по уже выбранному чипу сбрасывает значение в null.
Набор фильтров без выбранного значения осмыслен («любой»), и отменить выбор
иначе было бы нечем — в отличие от переключателей формы, где пустое значение
обычно запрещено.
Состав рисует потребитель
Пропа options у группы нет: у каждого чипа своя подпись, иконка и тон, и
генерировать их из плоского массива значило бы завести второй, более бедный
способ описать то же самое. Отсюда же remove — группа его только
ретранслирует, а убирает элемент из массива приложение.
Границы
- своего порядка у группы нет — она рисует чипы в порядке слота;
- группа не ограничивает число выбранных. Предел — правило приложения, и сообщение о нём тоже: группа не знает, что показать вместо отказа;
- вложенных групп не бывает. Набор внутри набора даёт неразрешимый вопрос, чья стрелка обрабатывается.
Playground 10
Загружается…
<GrChipGroup />Установка
npm i @feugene/granularityИмпорт
import { GrChipGroup } from '@feugene/granularity/components/GrChipGroup'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
tone | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined | undefined | — |
closable | boolean | undefined | false | Крестик у всех чипов набора. Точечно перебивается пропом самого чипа. |
modelValue | GrChipValue | GrChipValue[] | null | undefined | undefined | Одиночный выбор — значение, множественный — массив. |
disabled | boolean | undefined | false | — |
readonly | boolean | undefined | false | Выбор видно, но он не меняется. |
invalid | boolean | undefined | false | — |
required | boolean | undefined | false | — |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | — |
ariaLabel | string | undefined | undefined | — |
name | string | undefined | undefined | Имя для нативной формы. Множественный выбор отдаёт по полю на значение. |
dark | boolean | undefined | undefined | — |
radius | GrBadgeRadius | undefined | undefined | — |
selection | GrChipSelection | undefined | "multiple" | — |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Чипы набора. |
Events
| Event | Type | Описание |
|---|---|---|
update:modelValue | [value: GrChipValue | GrChipValue[] | null] | — |
change | [value: GrChipValue | GrChipValue[] | null] | — |
focus | [event: FocusEvent] | — |
blur | [event: FocusEvent] | — |
remove | [value: GrChipValue] | — |
Methods / Expose
| Methods / Expose | Type | Описание |
|---|---|---|
focus | () => void | undefined | — |
blur | () => void | undefined | — |
Примеры 3
Фильтры списка
Множественный выбор: видно все варианты сразу, выбранных может быть несколько.
<script setup lang="ts">
import { computed, ref } from 'vue'
import { GrCard, GrChip, GrChipGroup } from '@feugene/granularity'
const statuses = [
{ value: 'open', label: 'Открытые' },
{ value: 'review', label: 'На ревью' },
{ value: 'blocked', label: 'Заблокированные' },
{ value: 'done', label: 'Готовые' },
]
const selected = ref<string[]>(['open', 'review'])
const summary = computed(() => (selected.value.length
? statuses.filter(status => selected.value.includes(status.value)).map(s => s.label).join(', ')
: 'любой статус'))
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
<GrChipGroup v-model="selected" aria-label="Статус задачи">
<GrChip
v-for="status in statuses"
:key="status.value"
:value="status.value"
:label="status.label"
tone="primary"
/>
</GrChipGroup>
<GrCard class="p-4 text-sm text-[var(--gr-muted-fg)]">
Показываем: <span class="font-semibold text-[var(--gr-fg)]">{{ summary }}</span>
</GrCard>
</div>
</template>Один выбор с отменой
Период отчёта. Повторное нажатие на выбранный чип возвращает «весь».
<script setup lang="ts">
import { ref } from 'vue'
import { GrCard, GrChip, GrChipGroup } from '@feugene/granularity'
const periods = [
{ value: 'day', label: 'День' },
{ value: 'week', label: 'Неделя' },
{ value: 'month', label: 'Месяц' },
{ value: 'quarter', label: 'Квартал' },
]
// Повторное нажатие на выбранный чип сбрасывает период в «любой».
const period = ref<string | null>('week')
</script>
<template>
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
<GrChipGroup v-model="period" selection="single" tone="info" aria-label="Период отчёта">
<GrChip
v-for="item in periods"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</GrChipGroup>
<GrCard class="p-4 text-sm text-[var(--gr-muted-fg)]">
Период: <span class="font-semibold text-[var(--gr-fg)]">{{ period ?? 'весь' }}</span>
</GrCard>
</div>
</template>Набор фильтров без выбранного значения осмыслен, поэтому одиночный выбор снимается.
Выбор и снятие вместе
Метки записи, которые можно и отметить, и убрать.
Стрелки водят фокус по набору, Delete снимает метку под фокусом.
<script setup lang="ts">
import { ref } from 'vue'
import type { GrChipValue } from '@feugene/granularity'
import { GrChip, GrChipGroup } from '@feugene/granularity'
// Метки можно и выбирать, и снимать: выбор ведёт группа, состав — приложение.
const labels = ref(['срочно', 'бэкенд', 'регресс', 'релиз 0.25'])
const active = ref<string[]>(['срочно'])
function drop(value: GrChipValue): void {
const label = String(value)
labels.value = labels.value.filter(item => item !== label)
active.value = active.value.filter(item => item !== label)
}
</script>
<template>
<div class="grid gap-3">
<GrChipGroup v-model="active" closable aria-label="Метки записи" @remove="drop">
<GrChip
v-for="label in labels"
:key="label"
:value="label"
:label="label"
tone="warning"
/>
</GrChipGroup>
<p class="text-sm text-[var(--gr-muted-fg)]">
Стрелки водят фокус по набору, Delete снимает метку под фокусом.
</p>
</div>
</template>Набор — одна остановка Tab: внутрь ведут стрелки, Delete снимает чип под фокусом.
Доступность
- Паттерн APG
listbox / radiogroup (roving tabindex)- Клавиши
- Набор — одна остановка
Tab, внутрь ведут стрелки по обеим осям (чипы переносятся на новую строку),Home/End— к краям,Delete/Backspace— снять чип под фокусом. **Стрелка двигает только фокус, даже в одиночном режиме**: у чипа есть второе действие, и менять значение по дороге к нужному чипу нельзя