Files
vidconf/design/tailwind.md

237 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Маппинг токенов в 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-директив:
```css
@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:
```ts
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`:
```ts
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`:
```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 `outline` — `border-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]` на карточке).