Перейти к содержанию

Кастомизация темы Ant Design

Уровень: L3 (deep-dive) | Вверх: README.md | Раздел: ../README.md

Обзор

Ant Design 5 использует Design Token System — многоуровневую систему токенов для кастомизации внешнего вида. В проекте «Плечики» тема настроена через объект ThemeConfig, передаваемый в <ConfigProvider theme={antdTheme}> в App.tsx.

Кастомизация затрагивает: - Global Tokens — базовые цвета, радиусы, шрифты - Component Tokens — настройки конкретных компонентов (Button, Input, Card и др.)


Подключение

Файл: App.tsx

import { ConfigProvider } from 'antd';
import ruRU from 'antd/locale/ru_RU';
import { antdTheme } from './theme/antdTheme';

function App() {
  return (
    <ConfigProvider theme={antdTheme} locale={ruRU}>
      {/* ... */}
    </ConfigProvider>
  );
}

Полный код antdTheme.ts

Файл: frontend/src/theme/antdTheme.ts (113 строк)

import type { ThemeConfig } from 'antd';
import { tokens } from './tokens';
import { semanticTokens } from './semanticTokens';

export const antdTheme: ThemeConfig = {
  token: {
    // ── Цвета ──
    colorPrimary: semanticTokens.colorAccent,           // #D18A62
    colorBgBase: semanticTokens.colorBg,                // #F7F4EF
    colorTextBase: semanticTokens.colorText,            // #1F1B16
    colorBgContainer: semanticTokens.colorSurface,      // #FFFDFB
    colorBorder: semanticTokens.colorBorder,            // rgba(31,27,22,0.08)
    colorBorderSecondary: semanticTokens.colorBorder,
    colorText: semanticTokens.colorText,
    colorTextSecondary: semanticTokens.colorTextSecondary,
    colorTextTertiary: semanticTokens.colorTextMuted,
    colorTextQuaternary: semanticTokens.colorTextMuted,
    colorLink: semanticTokens.colorAccent,

    // ── Радиусы ──
    borderRadius: parseInt(tokens.radius.sm),            // 16
    borderRadiusSM: parseInt(tokens.radius.sm),          // 16
    borderRadiusLG: parseInt(tokens.radius.md),          // 18

    // ── Шрифт ──
    fontFamily: tokens.font.body,                        // 'Geist', sans-serif

    // ── Тени ──
    boxShadow: tokens.shadow.card,
    boxShadowSecondary: tokens.shadow.floating,

    // ── Размеры ──
    controlHeight: parseInt(tokens.control.md),          // 52
    controlHeightLG: parseInt(tokens.control.input),     // 54
    controlHeightSM: parseInt(tokens.control.sm),        // 38
  },

  components: {
    // ── Layout ──
    Layout: {
      bodyBg: semanticTokens.colorBg,
    },

    // ── Button ──
    // Pill-shaped кнопки без тени
    Button: {
      borderRadius: 999,              // Полное скругление (pill)
      borderRadiusLG: 999,
      borderRadiusSM: 999,
      primaryShadow: 'none',          // Без тени у primary
      dangerShadow: 'none',
      defaultShadow: 'none',
      fontWeight: 700,
      paddingInlineLG: 28,
      controlHeightLG: parseInt(tokens.control.md),  // 52
    },

    // ── Input ──
    // Мягкий фон, акцентная обводка при фокусе
    Input: {
      activeBg: tokens.color.surfaceAlt,              // #F2ECE3
      hoverBg: tokens.color.surfaceAlt,
      activeBorderColor: tokens.color.accent,         // #D18A62
      hoverBorderColor: tokens.color.accentHover,     // #C47D56
      activeShadow: `0 0 0 2px ${tokens.color.accentSoft}`,  // Мягкое свечение
      controlHeightLG: parseInt(tokens.control.input), // 54
      borderRadius: parseInt(tokens.radius.sm),        // 16
      borderRadiusLG: parseInt(tokens.radius.sm),
    },

    // ── Select ──
    Select: {
      controlHeightLG: parseInt(tokens.control.input),
      borderRadius: parseInt(tokens.radius.sm),
      borderRadiusLG: parseInt(tokens.radius.sm),
    },

    // ── Card ──
    Card: {
      borderRadiusLG: parseInt(tokens.radius.card),    // 22
      boxShadow: tokens.shadow.card,
      paddingLG: parseInt(tokens.space[4]),             // 16
    },

    // ── Drawer (BottomSheet) ──
    Drawer: {
      borderRadius: parseInt(tokens.radius.sheet),     // 28
      paddingLG: parseInt(tokens.space[6]),             // 24
    },

    // ── Modal ──
    Modal: {
      borderRadiusLG: parseInt(tokens.radius.card),
    },

    // ── Tag ──
    Tag: {
      borderRadiusSM: parseInt(tokens.radius.pill),
    },

    // ── Typography ──
    Typography: {
      fontFamilyCode: tokens.font.data,                // 'Geist Mono', monospace
    },
  },
};

Что изменено относительно дефолта Ant Design

Аспект Ant Design default Плечики Причина
Primary color #1677ff (синий) #D18A62 (терракотовый) Фирменный цвет бренда
Background #ffffff (белый) #F7F4EF (бежевый) Тёплая палитра
Border radius 6px 16px Mobile-first: крупные скругления
Button radius 6px 999px (pill) Дизайн-система: pill-кнопки
Button shadow 0 2px 0 rgba(...) none Чистый flat-дизайн
Button font weight 400 700 Акцент на CTA
Control height 32px 52px Мобильные тач-таргеты
Input height 32px 54px Увеличенная зона нажатия
Input background white #F2ECE3 Мягкий контраст с фоном
Card radius 8px 22px Современный mobile-дизайн
Drawer radius 0 28px Rounded bottom sheet
Font family -apple-system, ... 'Geist', sans-serif Кастомный шрифт

Полный код semanticTokens.ts

Файл: frontend/src/theme/semanticTokens.ts (39 строк)

import { tokens } from './tokens';

export const semanticTokens = {
  // Цвета приложения
  colorBg: tokens.color.bg,
  colorSurface: tokens.color.surface,
  colorSurfaceAlt: tokens.color.surfaceAlt,
  colorSurfaceOverlay: tokens.color.surfaceOverlay,
  colorSurfaceInverse: tokens.color.surfaceInverse,

  colorText: tokens.color.text,
  colorTextSecondary: tokens.color.textSecondary,
  colorTextMuted: tokens.color.textMuted,
  colorTextInverse: tokens.color.textInverse,

  colorAccent: tokens.color.accent,
  colorAccentSoft: tokens.color.accentSoft,
  colorAccentHover: tokens.color.accentHover,

  colorBorder: tokens.color.border,
  colorSuccess: tokens.color.success,
  colorWarning: tokens.color.warning,
  colorDanger: tokens.color.danger,
  colorDangerSoft: tokens.color.dangerSoft,

  // Chip dot colors
  chipDot: '#A7B29A',
  chipDotSoft: '#E4EADE',

  // Aliases
  radius: tokens.radius,
  space: tokens.space,
  control: tokens.control,
  layout: tokens.layout,
  shadow: tokens.shadow,
  z: tokens.z,
};

Полный код typography.ts

Файл: frontend/src/theme/typography.ts (61 строка)

import type { CSSProperties } from 'react';
import { tokens } from './tokens';

export const typography: Record<string, CSSProperties> = {
  display: {
    fontFamily: tokens.font.display,
    fontSize: tokens.fontSize.display,        // 28px
    fontWeight: tokens.fontWeight.bold,        // 700
    lineHeight: tokens.lineHeight.display,     // 1.1
    color: tokens.color.text,
  },

  pageTitle: {
    fontFamily: tokens.font.display,
    fontSize: tokens.fontSize.pageTitle,       // 24px
    fontWeight: tokens.fontWeight.bold,
    lineHeight: tokens.lineHeight.title,       // 1.15
    color: tokens.color.text,
  },

  sectionTitle: {
    fontFamily: tokens.font.display,
    fontSize: tokens.fontSize.sectionTitle,    // 20px
    fontWeight: tokens.fontWeight.bold,
    lineHeight: tokens.lineHeight.title,
    color: tokens.color.text,
  },

  body: {
    fontFamily: tokens.font.body,
    fontSize: tokens.fontSize.body,            // 15px
    fontWeight: tokens.fontWeight.regular,     // 400
    lineHeight: tokens.lineHeight.body,        // 1.4
    color: tokens.color.text,
  },

  caption: {
    fontFamily: tokens.font.body,
    fontSize: tokens.fontSize.caption,         // 13px
    fontWeight: tokens.fontWeight.medium,      // 500
    lineHeight: tokens.lineHeight.caption,     // 1.25
    color: tokens.color.textMuted,
  },

  meta: {
    fontFamily: tokens.font.body,
    fontSize: tokens.fontSize.meta,            // 11px
    fontWeight: tokens.fontWeight.medium,
    lineHeight: tokens.lineHeight.caption,
    color: tokens.color.textMuted,
  },

  data: {
    fontFamily: tokens.font.data,
    fontSize: tokens.fontSize.caption,
    fontWeight: tokens.fontWeight.medium,
    lineHeight: tokens.lineHeight.caption,
    color: tokens.color.text,
  },

  action: {
    fontFamily: tokens.font.body,
    fontSize: '14px',
    fontWeight: tokens.fontWeight.semibold,    // 600
    lineHeight: tokens.lineHeight.caption,
    color: tokens.color.accent,
  },
};