Первоначальная версия VidConf
This commit is contained in:
236
design/tailwind.md
Normal file
236
design/tailwind.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Маппинг токенов в 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()` (см. категории 3–4 в
|
||||
§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.12–4.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]` на карточке).
|
||||
Reference in New Issue
Block a user