195 lines
9.2 KiB
Markdown
195 lines
9.2 KiB
Markdown
# 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
|