DOP App — Тонке налаштування теми¶
Як крутити шрифти, кольори, density з єдиних точок контролю. Створено в session typography-overhaul (2026-05-09).
Локації джерел правди:
- frontend/erp/src/theme/typography.ts — шрифти (розмір, вага, сім'я, line-height) + 3-рівневий color registry
- frontend/erp/src/theme.ts — фон-палітри (tints), текстові кольори (light/dark), read-only input bg, density multipliers
- frontend/erp/src/global.css — CSS-змінні шрифт-сімей + правило для :read-only inputs
- frontend/erp/src/pages/SettingsPage/AppearanceSettings.tsx — UI вибору skin/tint/density
1. Архітектура — чому 4 файли, не один¶
| Файл | Що контролює | Хто читає |
|---|---|---|
typography.ts |
Шрифти (форма): розміри, ваги, line-height, сім'я | React-компоненти через T.<role> |
theme.ts |
Кольори (значення): фон, текст, read-only bg | Mantine CSS resolver → --mantine-color-* змінні |
global.css |
Глобальні CSS-правила (body { font-weight }, :read-only стиль) |
Браузер напряму |
AppearanceSettings.tsx |
UI для skin/tint/density | Юзер |
Розділення: типографія = форма (як виглядає шрифт), кольори = значення (text/dimmed/error). Не змішуємо. Один T.listRow може мати text-color у звичайному рядку і dimmed у disabled.
2. Типографія — як крутити¶
2.1 Глобальний розмір (всі шрифти одночасно)¶
typography.ts → редагуй BASE_FONT_SIZE:
Уся шкала fontSize.{xxs..xxl} рахується як BASE + delta. Зміниш на 16 → весь додаток стане крупнішим на 2px. Сценарії:
| Значення | Враження |
|---|---|
| 13 | Дуже щільно, 1С-стиль |
| 14 | Mantine sm equivalent, дефолт |
| 15 | Комфортніше для довгої роботи |
| 16 | Mantine md, "веб-стандарт" |
2.2 Глобальна щільність по вертикалі¶
| Значення | Враження |
|---|---|
| 1.30 | Дуже щільно, термінал-стиль |
| 1.45 | Компактно (Mantine sm) |
| 1.55 | Стандарт (Mantine md), повітряніше |
2.3 Конкретна роль (наприклад "header списку bold")¶
Кожна семантична роль — окремий токен у T:
listColumnHeader: {
fontSize: fontSize.md,
fontWeight: fontWeight.regular, // ← хочеш bold? зміни на semibold/bold
fontFamily: fontFamily.data,
lineHeight: BASE_LINE_HEIGHT,
},
Зміна → автоматично пропагується в усі мігровані компоненти-споживачі (15+ списків через <ListTable>, ще 14+ кастомних list файлів).
2.4 Карта токенів — що де використовується¶
| Токен | Де |
|---|---|
T.appBrand |
"Digital Operations Platform" в хедері |
T.breadcrumb / breadcrumbCurrent |
хлібні крихти в хедері |
T.navModule / navPlugin |
sidebar модулі / plugin-діти |
T.navGroupLabel |
"Всі функції" drawer title |
T.sectionGroupTitle / sectionCardTitle / sectionCardItem |
section dashboard (хаб модуля) |
T.listColumnHeader / listRow / listRowTotals |
усі списки (документи + master data) |
T.linesTableHeader |
шапки рядків у документі (LinesTable) |
T.formFieldLabel / formFieldValue / formTab / formSectionTitle |
форми редагування |
T.toolbarButton / toolbarMenuItem |
тулбари |
T.reportColumnHeader / reportRow / reportSubtotals / reportTotals |
звіти |
T.kpiLabel / kpiValue |
KPI-чіпи у footer'ах документів |
T.caption |
дрібні підказки/мета |
T.code |
inline monospace |
2.5 Шрифтова сім'я (для скінів)¶
Сім'ї керуються через CSS-змінні в global.css:
:root {
--eswf-font-ui: var(--mantine-font-family); /* chrome */
--eswf-font-data: var(--mantine-font-family); /* списки/звіти */
--eswf-font-form: var(--mantine-font-family); /* форми */
}
Скіни (наприклад terminal) перевизначають --eswf-font-ui на mono — і автоматично всі chrome-токени переключаються на mono без правки коду.
3. Кольори — 3 рівні + read-only bg¶
3.1 3 рівні текстового контрасту¶
const LIGHT_TEXT_COLORS = {
text: '#1a1a1a', // primary — Mantine c="text"
muted: '#4a4a4a', // НОВИЙ middle-рівень
dimmed: '#6a6a6a', // soft — Mantine c="dimmed"
};
const DARK_TEXT_COLORS = {
text: '#e8e8e8',
muted: '#b0b0b0',
dimmed: '#8a8a8a',
};
Як використовувати:
- <Text c="text"> / c="dimmed" — Mantine props (працюють through CSS vars)
- <Text style={{...T.caption, color: C.muted}}> — для middle-рівня (Mantine c= його не знає)
Як змінити: просто редагуй числа. Темну тему dimmed зробити темнішим? '#7a7a7a'. Зберігай — HMR підхопить, увесь додаток оновиться.
3.2 Per-tint конфігурація (light theme)¶
Кожен тинт (blue/green/yellow/neutral) має свій inputReadonlyBg у palettes:
blue: {
body: '#e0e8f2', // фон сторінки
white: '#f0f4fa', // editable input bg
default: '#e8eef6', // surface (Card/Paper)
...
inputReadonlyBg: '#d8e0eb', // ← READ-ONLY input
},
Read-only фон навмисно темніший за white — щоб юзер бачив "це поле не редагується".
3.3 Як додати ще один рівень тексту (наприклад "subtle" між muted і dimmed)¶
- У
theme.tsдодай у обидваLIGHT_TEXT_COLORSіDARK_TEXT_COLORS: - У resolver додай у обидва blocks
'--eswf-color-text-subtle': ...COLORS.subtle - У
typography.tsCregistry додай:
Готово, можеш писати style={{ color: C.subtle }}.
3.4 Per-tint текст (опційно — продвинутий рівень)¶
Зараз text/muted/dimmed однакові для всіх 4 тинтів. Якщо захочеш різні:
- Перенести LIGHT_TEXT_COLORS поля у TintPalette interface
- Заповнити різні значення для blue/green/yellow/neutral
- У resolver міняти LIGHT_TEXT_COLORS.text → p.text
Цього ще не зроблено (одне значення для всіх вистачає в більшості випадків).
4. Density (щільність) — глобальний multiplier для відстаней¶
4.1 Як працює¶
Multiplier застосовується до усіх Mantine spacing-токенів (xs/sm/md/lg/xl) через CSS resolver. Юзер обирає в Налаштуваннях → весь UI стискається/розширяється.
4.2 На що впливає (що реагує)¶
✅ Mantine gap="xs|sm|md|lg|xl" (Group, Stack, SimpleGrid)
✅ Mantine p|px|py|m|mx|my="xs|sm|md|lg|xl"
✅ Висоти input через --mantine-vertical-spacing-*
✅ Будь-яке var(--mantine-spacing-X) у custom CSS
4.3 На що НЕ впливає (поки)¶
❌ T.<role>.fontSize — фіксовані px (Phase C завдання)
❌ em-padding (слідує за fontSize, не density)
❌ Hardcoded px (іконки size={14}, ROW_HEIGHT virtualizer-а)
Тому ефект density зараз помітний на формах/тулбарах, слабший на списках. Phase C (у todo.md) виправить — multiplier поширюватиметься і на typography. Тоді density-toggle = реальний 1С/Salesforce-style.
4.4 Як змінити "розмах" multiplier'а¶
Просто переписати числа:
export const DENSITY_SCALE = {
compact: 0.5, // -50%, дуже щільно
normal: 1.0,
comfortable: 1.6, // +60%, дуже просторо
};
Збережи → HMR оновить.
4.5 Auto-detect¶
При першому вході виконується detectDensityFromScreen():
≥ 2400px → comfortable (27" QHD/4K @100%, ultrawide)
1400-2399px → normal (стандарт desktop/laptop)
< 1400px → compact (малий 13-14" laptop)
Юзер може override toggle. Кнопка "Визначити для цього екрана" перезапускає детект (наприклад при переключенні на інший монітор).
5. em-spacing — як зробити, щоб відступ слідував за шрифтом¶
5.1 Принцип¶
<NavLink
py="0.4em" // 0.4 × локальний fontSize
style={{ fontSize: T.sectionCardItem.fontSize }} // встановлює "локальний"
/>
Em-units резолвляться відносно fontSize самого елемента. Якщо встановити fontSize на батька (наприклад Card) — всі em-діти всередині рахуватимуться від нього.
5.2 Конвертація px → em (cheat sheet)¶
Припускаючи fontSize ≈ 16px:
| px | em |
|---|---|
4px |
0.25em |
5px |
0.3em |
6px |
0.4em |
8px |
0.5em |
10px |
0.65em |
LineHeight в ratio (unitless):
| px (в context fontSize=16) | ratio |
|---|---|
| 22px | 1.4 |
| 24px | 1.5 |
| 26px | 1.6 |
| 32px | 2.0 |
5.3 Що вже мігровано на em¶
<ListTable>shared shell — рядки/шапки<MasterDataCustomList>shared shellSubgroupCardуSectionDashboardPage- 27 кастомних list-файлів (CELL_SX/HEAD_SX) з власними щільностями
5.4 Як мігрувати ще один компонент¶
Приклад: SubtableLine з py={6}, gap={4}. Конверсія:
<NavLink
py="0.4em" // 6px при fz=15
gap="0.25em" // 4px
style={{ fontSize: T.sectionCardItem.fontSize }} // ← обов'язково для em-context
/>
Без style.fontSize em резолвиться відносно Mantine-дефолту батька — і твоя зміна T.sectionCardItem не вплине.
6. Швидкі сценарії¶
"Хочу щоб усі шрифти були крупнішими"¶
→ BASE_FONT_SIZE = 16 у typography.ts.
"Хочу щоб у темній темі dimmed-текст був яскравішим"¶
→ DARK_TEXT_COLORS.dimmed = '#a8a8a8' у theme.ts.
"Хочу щоб шапки списків були bold (зараз як рядки)"¶
→ T.listColumnHeader.fontWeight = fontWeight.semibold (або bold) у typography.ts.
"Хочу щоб read-only поля сильніше виділялись"¶
→ palettes.<tint>.inputReadonlyBg зробити темнішим у theme.ts.
"Хочу більший розмах density (compact реально дрібний, comfortable реально просторий)"¶
→ DENSITY_SCALE.compact = 0.5, comfortable = 1.6 у theme.ts.
"Хочу замінити шрифтову сім'ю в chrome (header/sidebar)"¶
→ У global.css --eswf-font-ui: 'Roboto Mono', monospace;
або у скіні (наприклад terminal.ts): '--eswf-font-ui': "'JetBrains Mono', monospace".
"Хочу кастомний middle-color для тексту (subtle / hint)"¶
→ Див. § 3.3 — додавання нового рівня в C-registry.
7. Запобіжники — чого НЕ робити¶
❌ Не додавай color всередину T.<role> токенів — мішатиме форму і значення (один токен → один колір не масштабується)
❌ Не міняй Mantine theme.fontSizes.{xs..xl} напряму — зламає Tooltip/Notification внутрішні стилі
❌ Не використовуй магічні fontSize: NN інлайн у нових компонентах — додавай новий semantic токен у T або переуся існуючий
❌ Не пиши padding: '6px 10px' у нових list-cell стилях — використовуй '0.4em 0.65em' для density-aware behavior
8. Як перевіряти зміни¶
Vite HMR підхоплює зміни в typography.ts/theme.ts моментально без перезавантаження. Особливо корисно при крутінні чисел — побачиш ефект одразу на всіх сторінках, які відкриті в tabs.
9. Roadmap¶
- ✅ Phase A — em-spacing на cards/lists (зроблено)
- ✅ Phase B — global density toggle для Mantine spacing (зроблено)
- 🔮 Phase C — typography density-aware (відкладено, див. § Deferred / Ideas нижче)
- 🔲 Per-tint text colors (опційно, за бажанням)
- 🔲
FixedAssetFields.tsxcleanup (unusedFORM_FW)
🔮 Deferred / Ideas¶
Phase C — typography density-aware¶
Мотивація: density-toggle (compact/normal/comfortable) у Налаштуваннях впливає лише на Mantine --mantine-spacing-* змінні (через CSS resolver, ×0.7/1.0/1.35 multiplier). Шрифти НЕ масштабуються разом — тому ефект density візуально слабкий: на формах помітно, на списках слабо (бо там em-padding слідує за T.listRow.fontSize, який фіксований). Після цього density-toggle = реальний 1С/Salesforce-style "Compact / Normal / Comfortable" — один тумблер тисне ВСЕ.
Чому відкладено: поточний ефект density вже корисний (Mantine spacing scaling помітне); типографічна частина — polish, не блокер. Два шляхи реалізації: (A) точний — замінити числові fontSize у токенах T на CSS calc-strings типу 'calc(14px * var(--eswf-density, 1))', em-padding автоматично слідує; (B) грубий — на :root встановити font-size: calc(16px * var(--eswf-density, 1)), всі rem-units реагують, але typography.ts на px-числах все одно треба чіпати. Рекомендований — A. Estimated: ~1 день.
Trigger: нічим зайнятись у backlog АБО прямий запит користувача на «зробити density реально помітним на списках».