GrButtonGroup

  • ядро
  • Действия
  • API · 7 props

Берут, когда действия одного класса стоят рядом.

import { GrButtonGroup } from '@feugene/granularity/components/GrButtonGroup'

Обзор

Когда брать

  • действия одного класса стоят рядом — панель инструментов, ряд «Сохранить / Отмена», переключатели вида;
  • ряд должен читаться как одно целое — внутренние границы не задваиваются, скругления остаются по краям;
  • оформление задаётся один раз — size, tone и variant раздаются всем кнопкам разом;
  • группе нужно имя — ariaLabel объясняет, что объединяет эти кнопки.

Когда взять другое

НужноБерите
Выбирается значение, а не выполняется действиеGrSegmented
Действий много и они прячутсяGrDropdownMenu
Действие одноGrButton
Разделы с разным содержимымGrTabs

Группа кнопок и сегменты выглядят похоже и означают разное: здесь каждая кнопка делает что-то и после нажатия ничего не остаётся выбранным, там один из вариантов всегда активен.

Варианты все тона и виды — настоящим компонентом

Варианты · GrButtonGroup
tone \ variantprimarysecondaryoutlineghostghost-border
primary
neutral
success
warning
danger
info
slate
azure

Размеры

  • xs
  • sm
  • md
  • lg

Матрица живая: наводите и нажимайте. Состояний наведения, фокуса и нажатия здесь нет — они за браузером, а не за пропом.

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Сегментированный переключатель одного контекста

    Группа собирает соседние действия в один контрол: края склеены, нажатый сегмент — состояние экрана, а aria-label говорит, что именно переключают.

  • Размер, вид и тон заданы один раз

    size, variant и tone заданы на группе и доходят до каждой кнопки; проп самой кнопки по-прежнему сильнее — так опасное действие остаётся красным в нейтральном ряду.

  • Столбец и ряд с зазором

    orientation="vertical" собирает кнопки в столбец и переносит скругления на верхний и нижний края; :attached="false" даёт обычный ряд, где каждая кнопка сохраняет свои радиусы и границы.

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
tone
"primary" | "neutral" | "success" | "warning" | "danger" | ""primary" | "neutral" | "success" | "warning" | "danger" | "info" | "slate" | "azure" | undefined
undefinedТон всех кнопок группы. Проп самой кнопки сильнее.
variantGrButtonVariant | undefinedundefinedВес всех кнопок группы: `primary`, `outline`, `ghost` и т. д. Проп самой кнопки сильнее.
size"xs" | "sm" | "md" | "lg" | undefinedundefinedОформление, общее для кнопок группы. Проп самой кнопки сильнее.
ariaLabelstring | undefinedundefinedДоступное имя группы: без него кнопки читаются как несвязанные.
orientationGrButtonGroupOrientation | undefined"horizontal"Направление ряда: `vertical` ставит кнопки столбцом. По умолчанию `horizontal`.
attachedboolean | undefinedtrueСклеивать кнопки в один блок. `false` — обычный ряд с зазором: каждая кнопка сохраняет свои радиусы и границы. В лотке не действует.
appearanceGrButtonGroupAppearance | undefinedundefinedВид группы. `default` — ряд по `attached`; `tray` — лоток: звенья лежат в общей подложке с зазором, прозрачные, а выбранное (`aria-pressed="true"` или `aria-current`) — лист с рамкой поверх лотка. Вес и тон звена в лотке решает группа, свой проп кнопки сильнее. По умолчанию `default`; тема меняет его через `componentDefaults` («Глина»).

Слоты

SlotTypeОписание
defaultanyКнопки группы.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrButtonGroupAppearance, GrButtonGroupContext, GrButtonGroupConfigurableProps } from '@feugene/granularity/components/GrButtonGroup'

Файлdist/types/components/GrButtonGroup/config.d.ts

grButtonGroupConfigconst
const grButtonGroupConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrButtonGroup">;

Файлdist/types/components/shared/buttonGroupContext.d.ts

GrButtonGroupAppearancetype

Вид группы: обычный ряд (склеенный или с зазором) или лоток — звенья в общей подложке.

type GrButtonGroupAppearance = 'default' | 'tray';
GrButtonGroupContexttype

Оформление, общее для кнопок группы. Каждое поле необязательно: группа задаёт только то, что задали ей, остальное кнопка решает сама.

type GrButtonGroupContext = {
    size: ComputedRef<GrButtonSize | undefined>;
    variant: ComputedRef<GrButtonVariant | undefined>;
    tone: ComputedRef<GrButtonTone | undefined>;
    /** Необязателен: контекст строят и руками. */
    appearance?: ComputedRef<GrButtonGroupAppearance>;
};
useGrButtonGroupfunction

Контекст группы для GrButton. Порядок разрешения оформления получается такой: проп кнопки → группа → GrConfigProvider → дефолт. Группа ближе к кнопке, чем глобальный провайдер, поэтому она и должна побеждать его, но не собственный проп кнопки.

function useGrButtonGroup(): GrButtonGroupContext | undefined;

Файлdist/types/components/GrButtonGroup/defaults.d.ts

GrButtonGroupConfigurablePropsinterface

Пропы GrButtonGroup, которые можно задать глобально — темой (componentDefaults в theme.json) или GrConfigProvider.

interface GrButtonGroupConfigurableProps {
    appearance: GrButtonGroupAppearance;
}

Файлdist/types/components/GrButtonGroup/GrButtonGroup.vue.d.ts

GrButtonGroupOrientationtype
type GrButtonGroupOrientation = 'horizontal' | 'vertical';
GrButtonGroupPropsinterface
interface GrButtonGroupProps {
    /** Доступное имя группы: без него кнопки читаются как несвязанные. */
    ariaLabel?: string;
    /** Направление ряда: `vertical` ставит кнопки столбцом. По умолчанию `horizontal`. */
    orientation?: GrButtonGroupOrientation;
    /**
     * Склеивать кнопки в один блок. `false` — обычный ряд с зазором: каждая
     * кнопка сохраняет свои радиусы и границы. В лотке не действует.
     */
    attached?: boolean;
    /**
     * Вид группы. `default` — ряд по `attached`; `tray` — лоток: звенья лежат в
     * общей подложке с зазором, прозрачные, а выбранное (`aria-pressed="true"` или
     * `aria-current`) — лист с рамкой поверх лотка. Вес и тон звена в лотке
     * решает группа, свой проп кнопки сильнее. По умолчанию `default`; тема меняет
     * его через `componentDefaults` («Глина»).
     */
    appearance?: GrButtonGroupAppearance;
    /** Оформление, общее для кнопок группы. Проп самой кнопки сильнее. */
    size?: GrButtonSize;
    /** Вес всех кнопок группы: `primary`, `outline`, `ghost` и т. д. Проп самой кнопки сильнее. */
    variant?: GrButtonVariant;
    /** Тон всех кнопок группы. Проп самой кнопки сильнее. */
    tone?: GrButtonTone;
}

Использование из прозы библиотеки

Оформление одной строкой
vue
<GrButtonGroup aria-label="Период" size="sm" variant="outline" tone="neutral">
  <GrButton>День</GrButton>
  <GrButton>Неделя</GrButton>
  <GrButton tone="primary">Месяц</GrButton>
</GrButtonGroup>

size, variant и tone группы доходят до кнопок через контекст. Порядок разрешения: проп кнопки → группа → GrConfigProvider → дефолт — группа ближе к кнопке, чем глобальный провайдер, поэтому побеждает его, но не собственный проп кнопки.

Контекст доступен и снаружи — useGrButtonGroup(), если поверх группы строится свой контрол.

Обёртки не разрывают ряд

Склейка считает звенья группы — прямых потомков, которые сами кнопка или содержат кнопку. Поэтому кнопку можно завернуть в тултип, v-if-обёртку или роутерную ссылку, и ряд останется целым:

vue
<GrButtonGroup aria-label="Документ">
  <GrButton>Открыть</GrButton>
  <GrTooltip content="Копия появится рядом">
    <GrButton>Дублировать</GrButton>
  </GrTooltip>
</GrButtonGroup>

Не-кнопочный потомок (разделитель, подпись) в склейку не входит и скругление себе не забирает.

Ориентация и режим без склейки
ПропЧто делает
orientation="vertical"столбец: скругления переезжают на верхний и нижний края ряда
:attached="false"обычный ряд с зазором, каждая кнопка со своими радиусами
Лоток

appearance="tray" — звенья не склеены, а лежат в общей подложке с полями и зазором 2px. Невыбранное звено прозрачно, выбранное — лист с рамкой поверх лотка:

vue
<GrButtonGroup appearance="tray" size="sm" aria-label="Период">
  <GrButton :aria-pressed="range === 'day'" @click="range = 'day'">День</GrButton>
  <GrButton :aria-pressed="range === 'week'" @click="range = 'week'">Неделя</GrButton>
</GrButtonGroup>
  • Выбранное — по aria-pressed="true" или aria-current. Своего состояния у кнопки нет: выбранность — атрибут, который потребитель и так обязан поставить ради скринридера, и лоток читает его же.
  • Вес и тон звена решает лоток: прозрачное ghost, выбранное secondary, тон neutral. Свой проп кнопки сильнее, variant группы в лотке не действует, attached — тоже.
  • Подложка лотка — --gr-muted, хук --gr-button-group-tray-bg.

Лоток — оформление, поэтому его задаёт и тема (componentDefaults: так он включён у «Глины»), и GrConfigProvider.

Радиус

Внешние углы склеенной группы скругляет радиус ступени звеньев — тот же, что у отдельной кнопки (--gr-button-radius-<ступень> темы); внутренние группа гасит сама. Ручка приложения --gr-button-radius задаёт один радиус кнопкам и группам сразу, --gr-button-group-radius — только внешним углам групп:

css
.app { --gr-button-radius: 12px; }
Шов между solid-звеньями

Соседние primary-звенья в склеенной группе заливаются одним цветом и сливаются: сплит-кнопка «Опубликовать ▾» читалась бы одной кнопкой с шевроном в подписи. Поэтому второе из соседних solid-звеньев получает светлый шов в 1px — слева в горизонтальной группе, сверху в вертикальной.

Группа ставит только переменную --gr-button-divider, а рисует шов тень самой кнопки: собственный box-shadow из листа группы проиграл бы утилите кнопки при любой специфичности. Звено, обёрнутое тултипом или ссылкой, шва не получает.

Доступность

Корень объявлен role="group"; имя задаётся пропом ariaLabel — без него набор кнопок читается как несвязанный. Кнопка под курсором или с фокусом поднимается над соседями, поэтому фокус-кольцо не срезается наложением границ.