9.2 KiB
Frontend Guide: Patterns
Формы
Общие правила
Для базовых form elements использовать:
app-inputapp-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