# Модалка со скроллбаром внутри контента
**Контекст**: В проекте не было переиспользуемого компонента Modal. Каждая модалка создавалась ad-hoc — дублирование разметки (`fixed inset-0 z-50` оверлей + `stopPropagation` + backdrop). При этом скроллбар либо отсутствовал (риск выхода контента за viewport), либо был на оверлее, а не на внутреннем контенте модалки.
## Проблема
### До рефакторинга
```tsx
// ❌ EditModal: scroll на ОВЕРЛЕЕ, а не на контенте
e.stopPropagation()}>
{/* контент без скролла */}
// ❌ LoginModal, RegisterModal, ReworkModal: вообще без overflow
```
**Последствия**:
- При высоком контенте (форма редактирования, список полей) скроллбар появляется на body/оверлее — пользователь видит скроллбар за пределами модалки
- Модалки без `max-h` + `overflow-y-auto` могут выйти за границы экрана
- Дублирование кода: 7 мест с идентичной логикой оверлея + `stopPropagation`
## Решение
Единый компонент `Modal` с `max-h-[90vh] overflow-y-auto` на **внутреннем** контейнере:
```tsx
export default function Modal({ onClose, children, size = 'md', className = '' }: ModalProps) {
return (
e.stopPropagation()}
>
{children}
)
}
```
### Что изменилось
```mermaid
graph LR
A["До: 7 ad-hoc модалок"] --> B["После: 1 компонент Modal"]
B --> C["max-h-[90vh] на inner div"]
C --> D["overflow-y-auto на inner div"]
D --> E["Скроллбар внутри модалки"]
B --> F["Размеры: sm/md/lg/xl"]
B --> G["className override для фона"]
```
### Ключевое различие
| | До | После |
|---|---|---|
| Расположение `overflow-y-auto` | На оверлее (EditModal) или отсутствует | На внутреннем контейнере |
| Скроллбар | Снаружи модалки (оверлей/body) | Внутри модалки |
| DRY | Нарушен — 7 копий разметки | Соблюдён — 1 компонент |
| Background override | Разный в каждом | `className` prop |
## Переведённые модалки
| Компонент | Файл | `size` | Примечание |
|-----------|------|--------|------------|
| EditModal | `app/places/my/page.tsx` | `xl` | Был главной проблемой |
| LoginModal | `components/LoginModal.tsx` | `sm` | Кастомный фон через className |
| RegisterModal | `components/RegisterModal.tsx` | `sm` | Кастомный фон через className |
| AddLocationModal | `components/AddLocationModal.tsx` | `xl` | Упрощён, убран лишний wrapper |
| ReworkModal | `app/admin/page.tsx` | `md` | Новый скролл |
| CreateUserModal | `app/admin/users/page.tsx` | `sm` | Новый скролл |
| Delete confirm (2 шт.) | `places/my`, `admin` | `sm` | Новый скролл |
## Не затронуто
- **PlaceCardModal** (`MapView.tsx`) — не модалка, а bottom panel
- **MapPicker** — полноэкранный компонент
## Принцип
> Скроллбар должен быть частью модального контента, а не оверлея. Пользователь должен понимать границы модалки, и скроллбар внутри них это подчёркивает.
## Связанные заметки
- [[decision-modal-component-extraction]] — почему выбрали компонент, а не починку на месте
- [[frontend-api-client]]
- [[architecture-overview]]
## Источник
Задача 2026-07-04: «Если в модалке нужен скроллбар, то он должен быть внутри модалки».
## Теги
#frontend #react #modal #ux #refactoring #DRY