Gavia UIv1.0.0

WlButton

Кнопка действия с вариантами, размерами и состоянием загрузки.
Компоненты / Действия v0.1.0

Измените props и посмотрите результат. Настройки одинаковы для примера и кода.

Настройки

variantВизуальный вариант кнопки.
toneСемантический цвет, независимый от варианта оформления.
sizeРазмер кнопки.
densityВ режиме compact высота кнопки меньше.
typeНативный type кнопки.

Подпись «Добавить» передаётся в default-слот, иконка plus — в icon.

Пример

Нажатий: 0

Tab, затем Enter или Space

Loading заменяет иконку спиннером и блокирует повторный click. Disabled тоже блокирует действие. В этом примере нет формы: submit и reset меняют атрибут type, а поведение формы проверяйте в приложении.

Код для приложения

Vue SFC · текущие настройки
<script setup lang="ts">
import { WlButton, WlIcon } from "gavia-ui";
import { ref } from "vue";
const count = ref(0);
</script>

<template>
  <div class="wl-stack" data-space="md">
    <div>
      <WlButton v-bind='{"variant":"secondary","size":"md","density":"default","loading":false,"disabled":false,"block":false,"type":"button"}' @click="count++"><template #icon><WlIcon name="plus" /></template>{{ "Добавить" }}</WlButton>
    </div>
    <p role="status" class="wl-text-small">{{ "Нажатий:" }} {{ count }}</p>
  </div>
</template>

Все варианты

Сравните значения variant для основного, дополнительного действия и удаления.

primary
secondary
ghost
soft
link
primary + danger
ghost + danger
soft + danger

Выберите любой вариант: все восемь кнопок выполняют локальное действие.

Vue SFC · пример для приложения
<script setup lang="ts">
import { WlButton, type WlButtonVariant } from "gavia-ui";
import { ref } from "vue";
// The public union makes a missing or unsupported variant a type error.
const labels = {
  primary: "Создать",
  secondary: "Черновик",
  ghost: "Отмена",
  soft: "Поделиться",
  link: "Подробнее"
} satisfies Record<WlButtonVariant, string>;
const variants = Object.keys(labels) as WlButtonVariant[];
const lastAction = ref("");
</script>

<template>
  <div class="wl-stack" data-space="lg">
    <div class="button-variant-grid">
      <div
        v-for="variant in variants"
        :key="variant"
        class="wl-stack"
        data-space="sm"
      >
        <div><WlButton :variant="variant" @click="lastAction = labels[variant]">{{ labels[variant] }}</WlButton></div>
        <code class="wl-text-small">{{ variant }}</code>
      </div>
      <div class="wl-stack" data-space="sm">
        <div>
          <WlButton variant="primary" tone="danger" @click="lastAction = 'Удалить'">
            {{ "Удалить" }}
          </WlButton>
        </div>
        <code class="wl-text-small">primary + danger</code>
      </div>
      <div class="wl-stack" data-space="sm">
        <div>
          <WlButton variant="ghost" tone="danger" @click="lastAction = 'Удалить тихо'">
            {{ "Удалить тихо" }}
          </WlButton>
        </div>
        <code class="wl-text-small">ghost + danger</code>
      </div>
      <div class="wl-stack" data-space="sm">
        <div>
          <WlButton variant="soft" tone="danger" @click="lastAction = 'Архив'">
            {{ "Архив" }}
          </WlButton>
        </div>
        <code class="wl-text-small">soft + danger</code>
      </div>
    </div>
    <p class="wl-text-small wl-text-muted" role="status">
      {{ lastAction
        ? "Последнее действие: " + lastAction
        : "Выберите любой вариант: все восемь кнопок выполняют локальное действие." }}
    </p>
  </div>
</template>

<style scoped>
.button-variant-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  gap: var(--wl-space-lg);
  align-items: start;
}
</style>

Размеры и плотность

Четыре размера в обычной и компактной плотности. Сравните их сразу или скройте компактный ряд.

Обычная плотность

Компактная плотность

size выбирает размер, density="compact" уменьшает высоту. Размеры берутся из токенов выбранной темы.

Vue SFC · пример для приложения
<script setup lang="ts">
import { WlButton, WlCheckbox, type WlDensity, type WlSize } from "gavia-ui";
import { computed, ref } from "vue";
const labels = { xs: "XS", sm: "SM", md: "MD", lg: "LG" } satisfies Record<WlSize, string>;
const sizes = Object.keys(labels) as WlSize[];
const showCompact = ref(true);
const densities = computed<readonly WlDensity[]>(() => showCompact.value ? ["default", "compact"] : ["default"]);
</script>

<template>
  <div class="wl-stack" data-space="lg">
    <WlCheckbox v-model="showCompact">{{ "Показать компактный ряд" }}</WlCheckbox>
    <div
      v-for="density in densities"
      :key="density"
      class="wl-stack"
      data-space="sm"
      :data-button-density="density"
    >
      <h3 class="wl-text-label">{{ density === 'compact' ? "Компактная плотность" : "Обычная плотность" }}</h3>
      <div class="wl-inline" data-space="md">
        <WlButton
          v-for="size in sizes"
          :key="size"
          :size="size"
          :density="density"
          variant="primary"
        >{{ labels[size] }}</WlButton>
      </div>
    </div>
    <p class="wl-text-small wl-text-muted">{{ "size выбирает размер, density=\"compact\" уменьшает высоту. Размеры берутся из токенов выбранной темы." }}</p>
  </div>
</template>

Disabled и loading

Disabled и loading, а также пример сохранения с завершением, отменой и повтором.

Disabled блокирует действие. Loading тоже блокирует кнопку и заменяет icon-слот спиннером.

Запросы не отправляются. Завершение и отмена в этом примере выполняются вручную.

Нажмите «Сохранить», затем завершите или отмените локальную загрузку. Сохранений: 0.

Vue SFC · пример для приложения
<script setup lang="ts">
import { WlButton, WlCheckbox, WlIcon } from "gavia-ui";
import { ref } from "vue";
const noAccess = ref(false);
const loading = ref(false);
const saved = ref(0);
const message = ref("Нажмите «Сохранить», затем завершите или отмените локальную загрузку.");
function finish(): void {
  loading.value = false;
  saved.value++;
  message.value = "Сохранение завершено.";
}
function cancel(): void {
  loading.value = false;
  message.value = "Загрузка отменена. Можно повторить сохранение.";
}
</script>

<template>
  <div class="wl-stack" data-space="lg">
    <div class="wl-inline" data-space="md">
      <WlButton disabled aria-describedby="button-disabled-reason">{{ "Недоступно" }}</WlButton>
      <WlButton variant="primary" loading>{{ "Сохранение…" }}</WlButton>
    </div>
    <p id="button-disabled-reason" class="wl-text-small wl-text-muted">
      {{ "Disabled блокирует действие. Loading тоже блокирует кнопку и заменяет icon-слот спиннером." }}
    </p>
    <WlCheckbox v-model="noAccess">{{ "Нет прав на сохранение" }}</WlCheckbox>
    <div class="wl-inline" data-space="sm">
      <WlButton
        variant="primary"
        :disabled="noAccess"
        :loading="loading"
        aria-describedby="button-saving-reason"
        @click="loading = true"
      >
        <template #icon><WlIcon name="check" :size="16" /></template>
        {{ loading ? "Сохраняем…" : "Сохранить" }}
      </WlButton>
      <WlButton v-if="loading" @click="finish">{{ "Завершить сохранение" }}</WlButton>
      <WlButton v-if="loading" variant="ghost" @click="cancel">{{ "Отменить загрузку" }}</WlButton>
    </div>
    <p id="button-saving-reason" class="wl-text-small wl-text-muted">
      {{ noAccess
        ? "Для этого действия нужны права на редактирование."
        : "Запросы не отправляются. Завершение и отмена в этом примере выполняются вручную." }}
    </p>
    <p class="wl-text-small" role="status">{{ message }} {{ "Сохранений:" }} {{ saved }}.</p>
  </div>
</template>

Иконки и содержимое слотов

Иконки слева и справа, кнопка без текста и счётчик в default-слоте.

icon размещает иконку слева. В default-слот можно добавить иконку справа или счётчик. Кнопка без текста получает aria-label.

Нажмите кнопку и проверьте действие и доступное имя.

Vue SFC · пример для приложения
<script setup lang="ts">
import { WlButton, WlIcon } from "gavia-ui";
import { ref } from "vue";
const lastAction = ref("");
const unread = ref(3);
function readMessages(): void {
  unread.value = 0;
  lastAction.value = "Сообщения прочитаны";
}
</script>

<template>
  <div class="wl-stack" data-space="lg">
    <div class="wl-inline" data-space="md">
      <WlButton variant="primary" @click="lastAction = 'Новая задача'">
        <template #icon><WlIcon name="plus" :size="16" /></template>
        {{ "Новая задача" }}
      </WlButton>
      <WlButton @click="lastAction = 'Найти'">
        <template #icon><WlIcon name="search" :size="16" /></template>
        {{ "Найти" }}
      </WlButton>
      <WlButton @click="lastAction = 'Продолжить'">
        <span class="button-slot-inline">{{ "Продолжить" }}<WlIcon name="arrow-right" :size="16" /></span>
      </WlButton>
      <WlButton
        variant="ghost"
        tone="danger"
        aria-label="Удалить черновик"
        @click="lastAction = 'Черновик удалён'"
      >
        <template #icon><WlIcon name="trash" :size="16" /></template>
      </WlButton>
      <WlButton variant="soft" @click="readMessages">
        <span class="button-slot-inline">{{ "Сообщения" }}<strong>{{ unread }}</strong></span>
      </WlButton>
    </div>
    <p class="wl-text-small wl-text-muted">{{ "icon размещает иконку слева. В default-слот можно добавить иконку справа или счётчик. Кнопка без текста получает aria-label." }}</p>
    <p class="wl-text-small" role="status">
      {{ lastAction
        ? "Последнее действие: " + lastAction
        : "Нажмите кнопку и проверьте действие и доступное имя." }}
    </p>
  </div>
</template>

<style scoped>
.button-slot-inline { display: inline-flex; align-items: center; gap: var(--wl-space-sm); }
</style>

Ширина кнопки и отправка формы

Переключите block, заполните обязательное поле и сравните button, submit и reset.

Не менее трёх символов. Нативная проверка выполняется до submit.

Форма ещё не отправлена. Отправок: 0. Проверок черновика: 0.

Обычная WlButton имеет type="button" и не отправляет форму. Submit и reset задаются явно. Этот пример хранит результат локально.

Vue SFC · пример для приложения
<script setup lang="ts">
import { WlButton, WlCheckbox, WlInput } from "gavia-ui";
import { ref } from "vue";
const name = ref("");
const block = ref(true);
const submitted = ref(0);
const drafts = ref(0);
const message = ref("Форма ещё не отправлена.");
function submit(): void {
  submitted.value++;
  message.value = "Создан проект «" + name.value.trim() + "».";
}
function reset(): void {
  name.value = "";
  message.value = "Форма очищена.";
}
</script>

<template>
  <div class="wl-stack" data-space="lg">
    <WlCheckbox v-model="block">{{ "Кнопка отправки на всю ширину" }}</WlCheckbox>
    <form
      class="button-form-example wl-stack"
      data-space="md"
      aria-label="Создание проекта"
      @submit.prevent="submit"
      @reset.prevent="reset"
    >
      <div class="wl-stack" data-space="xs">
        <label for="button-form-name" class="wl-text-label">{{ "Название проекта" }}</label>
        <WlInput
          id="button-form-name"
          v-model="name"
          name="projectName"
          required
          minlength="3"
          pattern=".*\S.*"
          aria-describedby="button-form-hint"
        />
        <p id="button-form-hint" class="wl-text-small wl-text-muted">
          {{ "Не менее трёх символов. Нативная проверка выполняется до submit." }}
        </p>
      </div>
      <div data-button-form-submit>
        <WlButton type="submit" variant="primary" :block="block">{{ "Создать проект" }}</WlButton>
      </div>
      <div class="wl-inline" data-space="sm">
        <WlButton @click="drafts++">{{ "Проверить черновик" }}</WlButton>
        <WlButton type="reset" variant="ghost">{{ "Сбросить форму" }}</WlButton>
      </div>
    </form>
    <p class="wl-text-small" role="status">
      {{ message }} {{ "Отправок:" }} {{ submitted }}{{ ". Проверок черновика:" }} {{ drafts }}.
    </p>
    <p class="wl-text-small wl-text-muted">{{ "Обычная WlButton имеет type=\"button\" и не отправляет форму. Submit и reset задаются явно. Этот пример хранит результат локально." }}</p>
  </div>
</template>

<style scoped>
.button-form-example { width: 100%; max-width: 28rem; }
</style>

Когда использовать

  • primary — основное действие группы; secondary — дополнительное.
  • ghost и link — менее заметные действия; tone="danger" обозначает действие, которое может удалить данные.
  • block заполняет доступную ширину; size и density меняют размеры и отступы, сохраняя DOM-контракт.

Полный список вариантов берётся из манифеста в настройках и API. Для перехода по URL используйте ссылку; WlButton рендерит нативный button.