Хотите заказать веб-сайт? Связаться с нами

Mantine: Button, ActionIcon и Loader — всё в одном

В прошлом уроке мы изучили все основные компоненты ввода. Теперь пришло время научиться создавать интерактивные элементы, с помощью которых пользователь управляет приложением. В этом уроке мы разберем кнопки (Button), иконки-кнопки (ActionIcon) и индикаторы загрузки (Loader). Эти компоненты вы будете использовать в каждом проекте, поэтому важно знать все их возможности и правильные сценарии применения.

Mantine: Button, ActionIcon и Loader — всё в одном

Button – главный элемент управления

Button — это самый важный интерактивный компонент в любом приложении. С его помощью пользователь отправляет формы, подтверждает действия, переходит по ссылкам и выполняет другие ключевые операции.

Основные варианты

Mantine предлагает 6 встроенных вариантов кнопок:

import { Button, Group } from '@mantine/core';

function ButtonVariants() {
  return (
    <Group>
      <Button variant="filled">Filled</Button>
      <Button variant="light">Light</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="subtle">Subtle</Button>
      <Button variant="default">Default</Button>
      <Button variant="transparent">Transparent</Button>
    </Group>
  );
}

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

  1. filled — основной вариант для самых важных действий (отправка формы, сохранение)
  2. light — для второстепенных действий (отмена, закрытие)
  3. outline — для нейтральных действий, когда кнопка не должна привлекать много внимания
  4. subtle — для минималистичных интерфейсов, почти как текстовая ссылка
  5. default — нейтральный вариант, который не использует основной цвет

Цвета и размеры

Button поддерживает все цвета из темы через проп color , а также предустановленные размеры:

import { Button, Group } from '@mantine/core';

function ButtonColorsAndSizes() {
  return (
    <>
      <Group>
        <Button color="blue">Синяя</Button>
        <Button color="red">Красная</Button>
        <Button color="green">Зеленая</Button>
        <Button color="grape">Фиолетовая</Button>
        <Button color="orange">Оранжевая</Button>
      </Group>
      
      <Group mt="md">
        <Button size="xs">XS</Button>
        <Button size="sm">SM</Button>
        <Button size="md">MD</Button>
        <Button size="lg">LG</Button>
        <Button size="xl">XL</Button>
      </Group>
    </>
  );
}

Скругление и полная ширина

radius управляет скруглением углов, а fullWidth делает кнопку на всю ширину контейнера:

import { Button, Group } from '@mantine/core';

function ButtonRadiusAndWidth() {
  return (
    <>
      <Group>
        <Button radius="xs">XS</Button>
        <Button radius="sm">SM</Button>
        <Button radius="md">MD</Button>
        <Button radius="lg">LG</Button>
        <Button radius="xl">XL</Button>
        <Button radius={0}>Нет скругления</Button>
      </Group>
      
      <Button fullWidth mt="md">
        Кнопка на всю ширину
      </Button>
    </>
  );
}

Иконки на кнопках

Mantine рекомендует использовать иконки из библиотеки @tabler/icons-react:

npm install @tabler/icons-react
import { Button, Group } from '@mantine/core';
import { IconHeart, IconShoppingCart, IconUpload } from '@tabler/icons-react';

function ButtonWithIcons() {
  return (
    <Group>
      <Button leftSection={<IconHeart size={18} />}>
        Нравится
      </Button>
      
      <Button rightSection={<IconShoppingCart size={18} />}>
        В корзину
      </Button>
      
      <Button
        leftSection={<IconUpload size={18} />}
        variant="light"
      >
        Загрузить
      </Button>
    </Group>
  );
}

Градиентный вариант

Для создания ярких акцентных кнопок используйте variant="gradient":

import { Button, Group } from '@mantine/core';

function GradientButton() {
  return (
    <Group>
      <Button
        variant="gradient"
        gradient={{ from: 'blue', to: 'cyan', deg: 135 }}
      >
        Градиентная кнопка
      </Button>
      
      <Button
        variant="gradient"
        gradient={{ from: 'grape', to: 'pink', deg: 45 }}
        size="lg"
      >
        Яркая кнопка
      </Button>
    </Group>
  );
}

ActionIcon – иконка как кнопка

ActionIcon — это компонент для создания иконок-кнопок. Он идеально подходит для элементов управления, где нужна только иконка без текста: удаление, редактирование, настройки, переключение тем и т.д.

Базовое использование

import { ActionIcon, Group } from '@mantine/core';
import { IconTrash, IconPencil, IconSettings, IconMoon } from '@tabler/icons-react';

function ActionIconExample() {
  return (
    <Group>
      <ActionIcon aria-label="Удалить">
        <IconTrash size={18} />
      </ActionIcon>
      
      <ActionIcon aria-label="Редактировать" variant="light">
        <IconPencil size={18} />
      </ActionIcon>
      
      <ActionIcon aria-label="Настройки" variant="filled" color="blue">
        <IconSettings size={18} />
      </ActionIcon>
      
      <ActionIcon aria-label="Переключить тему" variant="default" size="lg">
        <IconMoon size={20} />
      </ActionIcon>
    </Group>
  );
}

Важно: ActionIcon не имеет встроенной подписи, поэтому всегда используйте aria-label для доступности. Это критично для пользователей скринридеров.

Размеры и цвета

import { ActionIcon, Group } from '@mantine/core';
import { IconHeart } from '@tabler/icons-react';

function ActionIconSizes() {
  return (
    <Group>
      <ActionIcon size="xs" aria-label="XS">
        <IconHeart size={12} />
      </ActionIcon>
      <ActionIcon size="sm" aria-label="SM">
        <IconHeart size={14} />
      </ActionIcon>
      <ActionIcon size="md" aria-label="MD">
        <IconHeart size={18} />
      </ActionIcon>
      <ActionIcon size="lg" aria-label="LG">
        <IconHeart size={22} />
      </ActionIcon>
      <ActionIcon size="xl" aria-label="XL">
        <IconHeart size={26} />
      </ActionIcon>
    </Group>
  );
}

Варианты и цвета

import { ActionIcon, Group } from '@mantine/core';
import { IconStar } from '@tabler/icons-react';

function ActionIconVariants() {
  return (
    <Group>
      <ActionIcon variant="filled" color="yellow" aria-label="Звезда">
        <IconStar size={18} />
      </ActionIcon>
      <ActionIcon variant="light" color="yellow" aria-label="Звезда">
        <IconStar size={18} />
      </ActionIcon>
      <ActionIcon variant="outline" color="yellow" aria-label="Звезда">
        <IconStar size={18} />
      </ActionIcon>
      <ActionIcon variant="subtle" color="yellow" aria-label="Звезда">
        <IconStar size={18} />
      </ActionIcon>
    </Group>
  );
}

Loader – индикатор загрузки

Loader — это самостоятельный компонент для отображения процесса загрузки. Он используется как внутри кнопок, так и отдельно на страницах и в оверлеях.

Типы загрузчиков

Mantine предлагает 3 встроенных типа: oval, bars и dots:

import { Loader, Group } from '@mantine/core';

function LoaderTypes() {
  return (
    <Group>
      <Loader type="oval" />
      <Loader type="bars" />
      <Loader type="dots" />
    </Group>
  );
}

Цвета и размеры

import { Loader, Group } from '@mantine/core';

function LoaderColorsAndSizes() {
  return (
    <>
      <Group>
        <Loader color="blue" />
        <Loader color="red" />
        <Loader color="green" />
        <Loader color="grape" />
      </Group>
      
      <Group mt="md">
        <Loader size={20} />
        <Loader size={30} />
        <Loader size={40} />
        <Loader size={50} />
      </Group>
    </>
  );
}

size принимает числа (переводятся в rem) или CSS-значения, например size="2rem".

Состояние загрузки в кнопках

Самая важная практическая задача — показывать загрузку во время отправки формы или выполнения асинхронной операции.

import { Button, Group } from '@mantine/core';
import { useState } from 'react';

function ButtonLoading() {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    // Имитация асинхронной операции
    await new Promise((resolve) => setTimeout(resolve, 2000));
    setLoading(false);
  };

  return (
    <Group>
      <Button loading onClick={handleClick}>
        Загрузить
      </Button>
      
      <Button
        loading={loading}
        onClick={handleClick}
        loaderProps={{ type: 'bars' }}
      >
        {loading ? 'Загрузка...' : 'Нажми меня'}
      </Button>
      
      <Button
        loading
        loaderPosition="right"
        leftSection={<span>✨</span>}
      >
        Справа
      </Button>
    </Group>
  );
}

Важные пропсы для загрузки кнопок

  1. loadingboolean, включает состояние загрузки
  2. loaderPosition'left' или 'right' (по умолчанию 'left')
  3. loaderProps — передает пропсы в Loader (тип, размер, цвет)
Робот собирает из блоков сайт

Собери свой код. Запусти сайт!

От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.

Собери свой первый проект под руководством практикующих разработчиков.

Подробнее о курсе

Состояние загрузки в ActionIcon

ActionIcon также поддерживает состояние загрузки:

import { ActionIcon, Group } from '@mantine/core';
import { IconRefresh, IconCheck } from '@tabler/icons-react';
import { useState } from 'react';

function ActionIconLoading() {
  const [loading, setLoading] = useState(false);

  const handleRefresh = async () => {
    setLoading(true);
    await new Promise((resolve) => setTimeout(resolve, 1500));
    setLoading(false);
  };

  return (
    <Group>
      <ActionIcon
        loading={loading}
        onClick={handleRefresh}
        color="blue"
        aria-label="Обновить"
      >
        <IconRefresh size={18} />
      </ActionIcon>
      
      <ActionIcon
        loading
        loaderProps={{ type: 'bars' }}
        aria-label="Загрузка"
      >
        <IconCheck size={18} />
      </ActionIcon>
    </Group>
  );
}

LoadingOverlay – оверлей с загрузкой

LoadingOverlay используется, чтобы заблокировать часть интерфейса во время загрузки данных. Очень полезен для форм и списков.

import { useDisclosure } from '@mantine/hooks';
import { LoadingOverlay, Button, Box, Text } from '@mantine/core';

function LoadingOverlayExample() {
  const [visible, { toggle }] = useDisclosure(false);

  return (
    <Box pos="relative" p="xl" maw={400} mx="auto" style={{ border: '1px solid #ccc', borderRadius: 8 }}>
      <LoadingOverlay
        visible={visible}
        zIndex={1000}
        overlayProps={{ radius: 'sm', blur: 2 }}
        loaderProps={{ color: 'blue', type: 'bars' }}
      />
      
      <Text ta="center" size="lg" fw={500}>Контент страницы</Text>
      <Text ta="center" c="dimmed" mt="sm">
        Этот контент блокируется оверлеем во время загрузки
      </Text>
      
      <Button onClick={toggle} fullWidth mt="md">
        {visible ? 'Скрыть' : 'Показать'} загрузку
      </Button>
    </Box>
  );
}

Важные моменты

  1. Родительский элемент должен иметь pos="relative"
  2. loaderProps передаются в Loader
  3. Элементы под оверлеем остаются фокусируемыми с клавиатуры — это следует учитывать в UX

Полный пример: форма с обратной связью

Давайте объединим все изученное в этом уроке и создадим полноценную форму с индикацией загрузки:

import { useForm } from '@mantine/form';
import {
  Button,
  ActionIcon,
  Loader,
  LoadingOverlay,
  TextInput,
  Box,
  Group,
  Paper,
  Text,
  Stack,
  Alert,
} from '@mantine/core';
import { IconSend, IconTrash, IconRefresh, IconCheck, IconAlertCircle } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useState } from 'react';

interface FormValues {
  name: string;
  email: string;
}

function CompleteFeedbackForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);

  const form = useForm<FormValues>({
    mode: 'uncontrolled',
    initialValues: {
      name: '',
      email: '',
    },
    validate: {
      name: (value) => (value.length < 2 ? 'Имя должно быть минимум 2 символа' : null),
      email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Некорректный email'),
    },
  });

  const handleSubmit = async (values: FormValues) => {
    setIsSubmitting(true);
    setIsSuccess(false);
    
    try {
      // Имитация отправки на сервер
      await new Promise((resolve) => setTimeout(resolve, 2500));
      
      setIsSuccess(true);
      notifications.show({
        title: 'Успешно!',
        message: `Данные для ${values.name} отправлены`,
        color: 'green',
        icon: <IconCheck size={18} />,
      });
      
      form.reset();
    } catch (error) {
      notifications.show({
        title: 'Ошибка',
        message: 'Не удалось отправить данные',
        color: 'red',
        icon: <IconAlertCircle size={18} />,
      });
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleReset = () => {
    form.reset();
    setIsSuccess(false);
    notifications.show({
      title: 'Сброшено',
      message: 'Форма очищена',
      color: 'blue',
    });
  };

  return (
    <Paper pos="relative" p="xl" withBorder maw={500} mx="auto">
      <LoadingOverlay
        visible={isSubmitting}
        loaderProps={{ type: 'bars', color: 'blue' }}
        overlayProps={{ blur: 2 }}
      />

      <Box component="form" onSubmit={form.onSubmit(handleSubmit)}>
        <Stack gap="md">
          {isSuccess && (
            <Alert
              icon={<IconCheck size={18} />}
              title="Готово!"
              color="green"
              withCloseButton
              onClose={() => setIsSuccess(false)}
            >
              Форма успешно отправлена
            </Alert>
          )}

          <TextInput
            label="Имя"
            placeholder="Введите имя"
            withAsterisk
            disabled={isSubmitting}
            key={form.key('name')}
            {...form.getInputProps('name')}
          />

          <TextInput
            label="Email"
            placeholder="your@email.com"
            withAsterisk
            disabled={isSubmitting}
            key={form.key('email')}
            {...form.getInputProps('email')}
          />

          <Group justify="space-between" mt="md">
            <Group>
              <Button
                type="submit"
                leftSection={isSubmitting ? <Loader size={16} /> : <IconSend size={18} />}
                loading={isSubmitting}
              >
                {isSubmitting ? 'Отправка...' : 'Отправить'}
              </Button>

              <ActionIcon
                onClick={handleReset}
                variant="subtle"
                color="gray"
                disabled={isSubmitting}
                aria-label="Сбросить форму"
              >
                <IconRefresh size={18} />
              </ActionIcon>
            </Group>

            <ActionIcon
              onClick={handleReset}
              color="red"
              variant="light"
              disabled={isSubmitting}
              aria-label="Очистить все"
            >
              <IconTrash size={18} />
            </ActionIcon>
          </Group>
        </Stack>
      </Box>
    </Paper>
  );
}

export default CompleteFeedbackForm;

Итоги

Сегодня вы освоили все основные компоненты для создания интерактивных элементов и обратной связи:

  1. Button — основной элемент управления с 6 вариантами, настройкой цвета, размера и иконок
  2. ActionIcon — компактная иконка-кнопка для второстепенных действий
  3. Loader — индикатор загрузки с 3 встроенными типами
  4. LoadingOverlay — блокировка интерфейса во время загрузки
  5. Состояние loading в кнопках и иконках для асинхронных операций

Что должно быть в вашем проекте после этого урока

  1. Создана форма с кнопкой отправки, имеющей состояние загрузки
  2. Реализована иконка-кнопка (ActionIcon) для вспомогательного действия
  3. Использован Loader отдельно от кнопок (в оверлее или на странице)
  4. Настроены уведомления (notifications) для обратной связи с пользователем
  5. Все ActionIcon имеют aria-label для доступности
  6. Реализован LoadingOverlay для блокировки интерфейса во время загрузки

В следующем уроке мы разберем компоновку и навигацию — научимся строить каркас приложения с AppShell, Navbar, Header и создавать отзывчивые макеты с Grid и Flex.

Теги: