Corp/docs/guides/frontend/04-PATTERNS.md

195 lines
9.2 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.

# Frontend Guide: Patterns
## Формы
### Общие правила
Для базовых form elements использовать:
- `app-input`
- `app-select`
- shared select-компоненты из `frontend/src/components/shared/SearchableSelect.tsx`
Не допускается:
- смешивать на одном экране несколько разных focus/hover contracts без причины
- строить поле через случайные border/background utility-классы, если это уже покрыто foundation layer
### Формы оцениваются по UX-паттерну
Формы нельзя насильно привести к одному layout-паттерну. Они должны оцениваться по типу:
- CRUD form
- compose form
- filter form
- settings form
- dialog action form
Следствие:
- карточки можно нормализовать массово
- формы требуют ручного review по сценарию использования
Для `filter form` дополнительно:
- toolbar и фильтрующий блок лучше держать отдельным feature-компонентом, если page-level файл уже управляет списком, detail modal и compose flow одновременно
- если рядом уже есть удачный плотный list shell, сначала сохранять его внешний контракт и выносить логику/leaf-блоки без агрессивного редизайна
### Dropdown-like UX
Если поле:
- требует поиска
- поддерживает multi-select
- отображает выбранные элементы как chips
- ведёт себя как адресная строка compose-интерфейса
то приоритет у shared select-компонентов, а не у локально стилизованного native `select`.
Причина:
- dropdown affordance должен быть единым по приложению
- локальные стрелки, паддинги и раскрытия быстро создают вторую дизайн-систему
- filter-toolbar с сортировкой и раскрытием фильтров тоже не должен бесконечно разрастаться внутри page-level JSX
### Person chips
Если secondary/detail-блок показывает конкретных людей:
- получателей
- участников
- согласующих
- исполнителей
- авторов
то приоритет у person-chip паттерна: `avatar + name` внутри capsule, а не у сырого текста или локально собранного badge.
Причина:
- человек считывается быстрее по аватару и имени, чем по строке текста
- однотипные participant-блоки не должны выглядеть по-разному в соседних feature-модулях
- это снижает визуальный дрейф между `requests`, `equipment`, `procurement`, `documents`, `calendar`
Референс по духу:
- `frontend/src/components/requests/RequestUserBadge.tsx`
### Object detail screens
Если один detail-route обслуживает и обычный просмотр, и управление объектом:
- базовый режим должен быть `showcase-first`, а не выглядеть как обрубленная админка
- management controls встраиваются в ту же anatomy, а не создают второй отдельный layout
- главный акцент у сильного header и одного основного рабочего блока
- secondary-настройки должны быть заметно спокойнее главного контента
Референс по духу:
- `frontend/src/app/departments/[id]/page.tsx`
### Compose forms
Compose-паттерн должен восприниматься как рабочее пространство, а не как список независимых инпутов.
Особенно это важно для:
- заявлений
- сообщений
- писем
- согласований
Референс по духу:
- `frontend/src/components/requests/RequestComposeModal.tsx`
Базовый контракт:
- адресные строки сверху
- основное содержание формы в центре
- служебные поля ниже и визуально спокойнее
- действия формы собраны в одном предсказуемом footer
- page-level файл держит orchestration, а длинный compose workspace лучше выносить в feature component
## Модалки
### Базовый shell
Для обычных модалок использовать:
- `frontend/src/components/ui/Modal.tsx`
Новый modal pattern нельзя создавать без отдельной причины.
### Геометрия модалок
Target state:
- modal shell: `rounded-2xl`
Текущее переходное состояние:
- mobile может быть компактнее по геометрии, но без отдельного visual contract
### Responsive-логика
Разница между mobile и desktop должна достигаться через:
- responsive spacing
- responsive sizing
- изменение плотности
- sticky header/footer при необходимости
Не должно быть:
- отдельной mobile-версии той же модалки
- дублирования структуры ради платформы
## App Shell
`frontend/src/components/AppShell.tsx` задаёт верхнеуровневый контракт приложения:
- shell background
- header
- global search
- mobile drawers
- навигацию
Следствия:
- feature-страницы не должны переизобретать глобальный shell
- глобальные системные элементы должны оставаться в одном контракте
- capsule-геометрия допустима на уровне shell controls, например у глобального поиска
## Target-pattern registry
| Элемент | Рекомендуемый primitive / радиус | Допустимые исключения | Статус |
| --- | --- | --- | --- |
| Основная секция страницы | `app-surface rounded-2xl` | hero/media pattern по отдельному решению | `ok` |
| Внутренний grouped block | `app-surface-muted rounded-xl` | `rounded-lg`, если блок очень компактный | `needs refactor` |
| Modal shell | `app-surface-elevated rounded-2xl` | переходное mobile state допустимо временно | `ok` |
| Обычный input / textarea / select | `app-input` / `app-select` + `rounded-lg` | нет | `ok` |
| Searchable dropdown trigger | shared `SearchableSelect*` + `rounded-lg` | compose inline-row может быть крупнее по shell | `ok` |
| Compose address row | shared select pattern на базе `app-input` | `rounded-2xl` допустим для compose-shell строки | `ok` |
| Inline comment composer | shared `CommentControls` + icon-only send action | feed/footer variant может добавлять attachment actions перед input | `ok` |
| Person chip / participant capsule | local feature wrapper вокруг avatar + name capsule | простой текст допустим только если аватар/ссылка не нужны и список не является заметной частью блока | `ok` |
| Helper / alert / selected block | `app-selected`, `app-feedback-*`, `app-surface-muted` + `rounded-lg`/`rounded-xl` | зависит от веса блока | `needs refactor` |
| Pills / status chips / avatar | `rounded-full` | нет | `ok` |
| `rounded-md` у новых карточек и полей | не использовать | только legacy до отдельной переработки | `legacy` |
| `rounded-[...]` без обоснования | не использовать | только согласованные special cases | `legacy` |
## Do / Don’t
### Делать
- использовать `globals.css` как foundation layer
- опираться на `app-surface`, `app-input`, `app-select`, `app-action-*`
- выбирать радиус из базовой шкалы
- расширять существующий shared primitive, если паттерн уже есть
### Не делать
- не вводить новый радиус “под один экран”
- не стилизовать native `select` локальными хаками, если нужен shared dropdown pattern
- не смешивать old-gray-contract и `app-*` contract в одном новом блоке
- не дублировать shared interaction pattern в page-level JSX