Files
vidconf/design/tailwind.md

13 KiB
Raw Permalink Blame History

Маппинг токенов в Tailwind (для frontend-dev)

Источник значений — design/tokens.css (единственный источник истины). Ниже — как подключить их в tailwind.config.ts проекта frontend/ через tailwind.config + CSS-переменные, без дублирования значений в JS.

1. Подключение

  1. Скопировать (или импортировать) design/tokens.css в frontend/src/styles/tokens.css, подключить в корневом index.css до Tailwind-директив:
    @import './tokens.css';
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
  2. В tailwind.config.ts не хардкодить hex — ссылаться на CSS-переменные, чтобы тема комнаты ([data-theme="room"]) и тёмная тема оболочки ([data-theme="dark"] / prefers-color-scheme: dark, см. ниже §1a) переопределяли значения без пересборки Tailwind:
import type { Config } from 'tailwindcss'

export default {
  darkMode: ['selector', '[data-theme="room"]'], // room-тема — не системная OS dark, а локальный скоуп
  content: ['./src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        bg: 'var(--color-bg)',
        'bg-alt': 'var(--color-bg-alt)',
        surface: 'var(--color-surface)',
        border: {
          DEFAULT: 'var(--color-border)',
          strong: 'var(--color-border-strong)',
          pill: 'var(--color-border-pill)',
        },
        ink: {
          900: 'var(--color-ink-900)',
          700: 'var(--color-ink-700)',
          500: 'var(--color-ink-500)',
          400: 'var(--color-ink-400)',
          300: 'var(--color-ink-300)',
        },
        // Акцент не обязан быть зелёным — см. DESIGN_SYSTEM.md §1.1.
        accent: {
          DEFAULT: 'var(--color-accent)',
          hover: 'var(--color-accent-hover)',
          active: 'var(--color-accent-active)',
          text: 'var(--color-accent-text)',
        },
        success: { DEFAULT: 'var(--color-success)', bg: 'var(--color-success-bg)', dot: 'var(--color-success-dot)' },
        danger: { DEFAULT: 'var(--color-danger)', bg: 'var(--color-danger-bg)', dot: 'var(--color-danger-dot)' },
        warning: { DEFAULT: 'var(--color-warning)', bg: 'var(--color-warning-bg)', dot: 'var(--color-warning-dot)' },
        focus: 'var(--color-focus-ring)',

        // Тема комнаты — доступна везде как room-* (реально применяется только
        // внутри [data-theme="room"], т.к. сами переменные там переопределены)
        room: {
          bg: 'var(--color-room-bg)',
          surface: 'var(--color-room-surface)',
          'surface-raised': 'var(--color-room-surface-raised)',
          tile: 'var(--color-room-tile)',
          'tile-border': 'var(--color-room-tile-border)',
          'tile-hover': 'var(--color-room-tile-hover)',
          'text-primary': 'var(--color-room-text-primary)',
          'text-secondary': 'var(--color-room-text-secondary)',
          'text-tertiary': 'var(--color-room-text-tertiary)',
          'mic-on': 'var(--color-room-mic-on)',
          'mic-off': 'var(--color-room-mic-off)',
          'speaker-ring': 'var(--color-room-speaker-ring)',
          danger: 'var(--color-room-danger)',
          'danger-bg': 'var(--color-room-danger-bg)',
          'danger-bg-hover': 'var(--color-room-danger-bg-hover)',
          focus: 'var(--color-room-focus-ring)',
        },
      },
      fontFamily: {
        display: ['Unbounded', 'Arial Rounded MT Bold', '-apple-system', 'Segoe UI', 'sans-serif'],
        body: ['Inter', '-apple-system', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', 'sans-serif'],
        mono: ['JetBrains Mono', 'SFMono-Regular', 'Menlo', 'Consolas', 'monospace'],
      },
      fontSize: {
        'display-2xl': ['64px', { lineHeight: '1.05', fontWeight: '700' }],
        'display-xl': ['48px', { lineHeight: '1.1', fontWeight: '700' }],
        'display-lg': ['34px', { lineHeight: '1.2', fontWeight: '600' }],
        h1: ['28px', { lineHeight: '1.25', fontWeight: '700' }],
        h2: ['22px', { lineHeight: '1.3', fontWeight: '600' }],
        h3: ['18px', { lineHeight: '1.35', fontWeight: '600' }],
        'body-lg': ['16px', { lineHeight: '1.5' }],
        body: ['14px', { lineHeight: '1.5' }],
        caption: ['12px', { lineHeight: '1.4', letterSpacing: '.04em', fontWeight: '600' }],
        'mono-sm': ['13px', { lineHeight: '1.4', fontWeight: '500' }],
      },
      spacing: {
        1: 'var(--space-1)', 2: 'var(--space-2)', 3: 'var(--space-3)',
        4: 'var(--space-4)', 5: 'var(--space-5)', 6: 'var(--space-6)',
        8: 'var(--space-8)', 10: 'var(--space-10)', 12: 'var(--space-12)',
        16: 'var(--space-16)', 20: 'var(--space-20)', 24: 'var(--space-24)',
      },
      borderRadius: {
        sm: 'var(--radius-sm)', md: 'var(--radius-md)', lg: 'var(--radius-lg)',
        xl: 'var(--radius-xl)', '2xl': 'var(--radius-2xl)', '3xl': 'var(--radius-3xl)',
        full: 'var(--radius-full)',
      },
      boxShadow: {
        sm: 'var(--shadow-sm)', md: 'var(--shadow-md)', lg: 'var(--shadow-lg)',
        glass: 'var(--shadow-glass)',
        'speaker-glow': 'var(--shadow-speaker-glow)',
        'room-panel': 'var(--shadow-room-panel)',
      },
      screens: {
        // desktop-first: минимальная поддерживаемая ширина — 1280px
        min: { raw: '(min-width: 1280px)' },
      },
      transitionDuration: {
        fast: '120ms',
        base: '200ms',
      },
    },
  },
  plugins: [],
} satisfies Config

1a. Тёмная тема оболочки — контракт для frontend

Механизм (детали и обоснование — design/DESIGN_SYSTEM.md §0.1/§1.2b, реализация значений — design/tokens.css):

  • Атрибут data-theme="dark" / data-theme="light" на <html> — явный выбор пользователя (persist в localStorage/профиле); без атрибута — дефолт по @media (prefers-color-scheme: dark).
  • Оба триггера переопределяют те же самые CSS-переменные, что и светлый :root (--color-bg, --color-ink-900, --color-accent, …). Так как весь маппинг Tailwind-цветов в §1 уже идёт через var(--color-*), а не хардкод, подавляющему большинству компонентов не нужна отдельная dark:-логика — те же классы (bg-bg, text-ink-900, bg-accent) автоматически перекрашиваются вместе со сменой data-theme.
  • Существующий Tailwind dark:-вариант (darkMode: ['selector', '[data-theme="room"]']) остаётся зарезервирован только за комнатой (см. комментарий в конфиге §1) — не путать с тёмной темой оболочки, у них разный контракт (комната не переключается, оболочка — переключается и по умолчанию следует ОС).
  • Для редких мест, где нужен именно Tailwind-вариант под тёмную оболочку (например, компонентные исключения §1.2b DESIGN_SYSTEM.md — glass-обводка, focus/error-glow, если их проще выразить class, а не var()) — завести отдельный кастомный вариант shell-dark: через addVariant, с тем же двойным условием, что и в tokens.css:
import plugin from 'tailwindcss/plugin'

export default {
  // ...
  plugins: [
    plugin(({ addVariant }) => {
      addVariant('shell-dark', [
        '&[data-theme="dark"]',
        '@media (prefers-color-scheme: dark) { &:not([data-theme]) }',
      ])
    }),
  ],
} satisfies Config

Использование: shell-dark:border-white/10 и т.п. — только для случаев, которые принципиально нельзя выразить через var() (см. категории 34 в §1.2b DESIGN_SYSTEM.md); для всех обычных цветов компонентов — var()-токены, shell-dark: не нужен.

2. shadcn/ui

components.json продолжает использовать CSS-переменные (cssVariables: true), но вместо стандартной shadcn-палитры (--primary, --secondary, …) — алиасить их на токены проекта в globals.css:

:root {
  --primary: var(--color-ink-700);
  --primary-foreground: var(--color-surface);
  --secondary: var(--color-bg-alt);
  --destructive: var(--color-danger);
  --destructive-foreground: #FFFFFF;
  --ring: var(--color-focus-ring);
  --radius: 24px; /* соответствует --radius-2xl, база для shadcn radius-cascade */
}

[data-theme='room'] {
  --primary: var(--color-room-mic-on);
  --destructive: var(--color-room-danger-bg);
  --ring: var(--color-room-focus-ring);
}

Тёмную тему оболочки ([data-theme='dark']) здесь отдельно объявлять не нужно: блок :root выше уже целиком построен на var(--color-*), а эти переменные сами переопределяются в tokens.css под [data-theme='dark']/ prefers-color-scheme — shadcn-компоненты перекрашиваются вместе с оболочкой автоматически, без дублирования правил.

Button variant default переопределить на pill (rounded-full) + bg-accent text-accent-text hover:bg-accent-hover active:bg-accent-active вместо стандартного bg-primary. Variant outlineborder-border-pill text-ink-700 rounded-full.

3. Иконки

lucide-react устанавливается как обычная npm-зависимость (не CDN), stroke-width={2} по умолчанию через обёртку <Icon /> в frontend/src/components/ui/icon.tsx, цвет — через currentColor (наследуется от текстового токена родителя). Список иконок по экранам — раздел 6 DESIGN_SYSTEM.md.

4. Паттерны динамических конференций — для frontend-dev

Макеты design/mockups/lobby.html, calendar.html, my-conferences.html, join.html вводят компоненты, которых нет в статичном списке комнат (см. DESIGN_SYSTEM.md §4.124.15). При переносе в frontend/:

  • Раскрытие тумблером («Закрытая по паролю», «Закрепить постоянную конференцию») и radio-группа повторений в HTML-макете сделаны чистым CSS через :has() (без JS/зависимостей) — во frontend это обычный controlled-компонент (useState<boolean> для тумблера, useState<RecurrenceType> для radio-группы), :has() не переносится.
  • Пилюли «ссылка/номер» с копированием (§4.15) — обёртка над navigator.clipboard.writeText, состояние «скопировано» — таймер setTimeout ~1.5s (как в статическом макете my-conferences.html), плюс aria-live="polite" для тултипа «Скопировано» (в макете — визуальный opacity-переход, без aria-live, добавить во frontend).
  • Бейдж статуса конференции (§4.6) заменяет прежний Badge для комнат: варианты scheduled / pinned / live / ended / cancelled; закрытость паролем — отдельная иконка Lock рядом с заголовком, не вариант Badge.
  • Карточки-действия хаба (§4.12) — фиксированная высота (не min-height) обязательна для симметрии сетки 2×2; в CSS-grid Tailwind — grid-rows-2 с явной высотой строки (grid-template-rows: repeat(2, 340px) через [grid-template-rows:repeat(2,340px)] или отдельный h-[340px] на карточке).