NODEDC_TASKMANAGER/HDROPDOWN-CANON.md

380 lines
18 KiB
Markdown
Raw 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.

# HDROPDOWN CANON
Документ фиксирует единый канон dropdown-окон NODE.DC.
Связанный техдолг:
- [plane-src/docs/technical-debts/dropdown-standardization-debt.md](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/docs/technical-debts/dropdown-standardization-debt.md)
Цель документа:
- убрать локальные самодельные реализации выпадающих окон
- перевести dropdown на переиспользуемые shared-компоненты
- зафиксировать единый контракт для trigger, portal, placement, стилей и поведения
- исключить повторение проблемы, когда соседние controls работают по-разному на одном и том же экране
## Базовый принцип
Dropdown в системе не является "локальной маленькой версткой рядом с кнопкой".
Dropdown это:
- отдельный floating-layer
- отдельный reusable UI-компонент
- предсказуемый контракт открытия и закрытия
- единый визуальный канон
Если на экране есть несколько выпадающих окон, пользователь не должен видеть разные механики:
- одно окно открывается по тексту
- второе только по стрелке
- третье клипается контейнером
- четвертое открывается с другим типом привязки
Это считается дефектом стандартизации.
## Какие dropdown бывают
В проекте фиксируются три типа выпадающих окон.
### 1. Selection dropdown
Используется там, где пользователь выбирает значение.
Примеры:
- `Статус`
- `Приоритет`
- `Дата`
- `Назначенные`
- `Метки`
- выбор проекта
- выбор модуля
Смысл:
- пользователь выбирает одно или несколько значений
- у dropdown есть список опций
- часто есть поиск
- текущее значение отображается в trigger
Канонические reference-реализации:
- [state/base.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/core/components/dropdowns/state/base.tsx:45)
- [priority.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/core/components/dropdowns/priority.tsx:298)
### 2. Action dropdown
Используется там, где пользователь вызывает набор действий.
Примеры:
- `...` на карточке задачи
- `...` в detail-header
- быстрые действия у рабочего элемента
Смысл:
- dropdown не меняет поле напрямую
- dropdown показывает список команд
- каждая строка запускает `action`
Канонический shared-компонент:
- [action-dropdown.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/packages/ui/src/dropdowns/action-dropdown.tsx:109)
### 3. Context menu
Используется только как контекстное меню, а не как основной UI для кнопки `...`.
Примеры:
- правый клик
- context-actions, привязанные к `parentRef`
Правило:
- `ContextMenu` не заменяет основной dropdown по клику на кнопку
- если у карточки есть кнопка `...`, основной visible menu должен открываться через `ActionDropdown`
- `ContextMenu` допустим как дополнительный способ вызвать те же действия через right click
## Что запрещено
Запрещено делать новый dropdown через:
- локальный `isOpen` без shared-компонента
- локальный `useOutsideClick` внутри карточки
- inline absolute-блок внутри карточки
- popup, который живет внутри scroll-контейнера
- отдельную реализацию `...`, если рядом уже есть рабочий shared-dropdown
- другой trigger-контракт для соседних controls на одной оси
Запрещено:
- делать одну механику для `Статуса`, а другую для `...`
- открывать меню только по стрелке, если весь control должен быть кликабельным
- класть popup внутрь карточки, detail-pane, sidebar или sticky-header без portal
- компенсировать неверную архитектуру случайными `left/right translate`
## Главный стандарт переиспользования
Перед созданием нового dropdown нужно ответить только на два вопроса:
1. Это выбор значения или набор действий?
2. Есть ли уже shared-компонент для этого типа?
Если это выбор значения:
- использовать существующий selection-dropdown
- расширять existing base-component, а не писать новый popup с нуля
Если это список действий:
- использовать `ActionDropdown`
- не создавать отдельную локальную механику для карточки, detail-view или sidebar
## Канонический стек для dropdown
Любой новый dropdown должен опираться на один и тот же стек:
- реальный `trigger`
- `usePopper`
- `portal`
- `outside click close`
- `keyboard close/open`
- `preventOverflow`
- `flip`
Для action dropdown текущий канон зафиксирован в:
- [action-dropdown.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/packages/ui/src/dropdowns/action-dropdown.tsx:118)
Для selection dropdown reference-стек уже зафиксирован в:
- [state/base.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/core/components/dropdowns/state/base.tsx:45)
## Trigger-правила
Trigger должен быть реальным интерактивным элементом.
Правила:
- dropdown привязывается к настоящей кнопке или surface-контролу
- нельзя заворачивать trigger в лишний интерактивный wrapper без причины
- нельзя делать `button` внутри `button`
- нельзя вешать отдельный click-interceptor на родительскую карточку так, чтобы он ломал trigger dropdown
Если control живет внутри кликабельной карточки:
- интерактивная зона обязана быть помечена как ignore-area для клика карточки
- карточка не должна перехватывать клик у dropdown trigger
Пример текущего канона для вложенной интерактивной зоны карточки:
- `data-control-link-ignore="true"`
## Portal-правила
Dropdown нельзя рендерить inline, если control находится внутри:
- карточки
- kanban-колонки
- list-item
- detail-pane
- sidebar
- properties section
- sticky toolbar
- scrollable container
В этих случаях popup обязан рендериться через portal на верхнем слое.
Базовое правило:
- `document.body` является стандартным portal target, если нет отдельного системного слоя
Причина:
- иначе будут клиппинг, неправильный z-index, обрезание соседними контейнерами и ложные оффсеты
## Placement-правила
Placement выбирается не "на глаз", а по канону control-type.
### Канон для selection dropdown
По умолчанию:
- `bottom-start`
Это уже используется соседними controls:
- `Статус`
- `Приоритет`
- `Дата`
### Канон для action dropdown карточек
Для quick-actions карточек используется тот же базовый anchor-канон, что и у соседних dropdown на той же верхней оси.
Текущая фиксация для kanban:
- [base-kanban-root.tsx](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx:126)
- quick-actions в kanban теперь идут через `bottom-start`
Смысл:
- меню раскрывается относительно trigger по той же логике, что и соседние controls
- мы не подгоняем offset вручную под один экран
- мы нормализуем сам placement-contract
### Когда допустим другой placement
Другой placement допустим только если это диктует layout:
- `top-start`, если dropdown открывается из нижней части detail-pane и не должен врезаться в секцию ниже
- `top-end`, если control находится у правой кромки и popup иначе уходит за экран
Но это должно быть обосновано layout-контекстом, а не случайной визуальной подгонкой.
## Offset-правило
Offset нужен только для вертикального зазора между trigger и popup.
Канон:
- вертикальный gap небольшой и стабильный
- боковой offset по умолчанию не используется
Если кажется, что popup "смотрит не туда":
- сначала проверяется правильность `placement`
- потом anchor element
- и только в последнюю очередь ручной skidding
Правило:
- нельзя лечить неверный `placement` случайным `left/right offset`
## Визуальный канон
Все dropdown и popup подчиняются общему matte glass стилю.
Обязательные свойства:
- темный glass surface
- blur
- мягкая стеклянная граница
- без технических outline
- без браузерного синего focus ring
- без светлой инородной подложки на темном экране
Внутренние элементы dropdown:
- search shell визуально того же семейства, что и popup
- option row того же радиуса, что и системные popup-option
- hover мягкий, не кислотный
- active/selected state не ломает общую палитру
## Правила для списка действий
Action dropdown обязан принимать данные в виде menu items.
Каждый item:
- имеет `key`
- может иметь `icon`
- может иметь `title`
- может иметь `description`
- может иметь `disabled`
- может иметь `action`
Правило:
- визуальный рендер item делается внутри shared action-dropdown
- вызывающий код не рисует popup shell вручную
## Правила для карточек и detail-view
Для карточек рабочего элемента:
- `...` всегда action dropdown
- `Статус` и `Приоритет` всегда selection dropdown
- все три control-а на одной оси должны использовать совместимую trigger-геометрию и близкий placement-contract
Для detail-view:
- quick actions сверху не должны использовать отдельный popup-engine
- если действия те же, используется тот же `ActionDropdown`
Для `Внутреннего контура`:
- quick-actions карточек больше не живут на отдельной локальной механике
- они нормализуются под тот же shared dropdown-stack, что и другие controls
## Правила для хлебных крошек
Breadcrumb dropdown тоже подчиняется этому документу.
Правило:
- project breadcrumb это dropdown проектов
- module breadcrumb это dropdown модулей проекта
- весь breadcrumb-item кликабелен, а не только стрелка
- стрелка не может быть единственной интерактивной зоной
Если breadcrumb открывает список выбора:
- это selection dropdown
- trigger-контракт должен совпадать на всех проектных экранах
## Правила для новых экранов
Новый экран не имеет права изобретать свой тип dropdown, если:
- уже существует shared action dropdown
- уже существует shared selection dropdown
Порядок работы:
1. определить тип dropdown
2. выбрать shared-компонент
3. выбрать canonical placement
4. открыть popup через portal
5. применить glass-канон
6. проверить поведение внутри scroll/sticky/detail contexts
## Что делать, если нужен новый вариант
Если текущих shared-вариантов не хватает:
- сначала расширяется shared-component
- потом новый вариант документируется в этом файле
- только после этого он применяется на экранах
Что нельзя делать:
- сделать новый локальный dropdown "временно"
- спрятать временную логику внутри конкретной карточки
- оставлять два разных menu-engine для одинаковой задачи
## Legacy-правило
Если в проекте еще остались старые dropdown-механики:
- они считаются legacy
- новые карточечные quick-actions на них не строятся
- при доработке экрана предпочтение отдается переводу на shared-канон, а не лечению локального бага поверх legacy-кода
Это особенно касается:
- локальных `CustomMenu`-вариантов для карточечных `...`
- inline popup внутри карточек
- отдельных `isMenuActive` состояний в item-компонентах
## Reference-матрица переиспользования
Использовать:
- `ActionDropdown`
- для `...`
- для быстрых действий карточки
- для action-menu detail-header
- `StateDropdown`
- для выбора состояния
- `PriorityDropdown`
- для выбора приоритета
- `DateDropdown`
- для выбора даты
- `MemberDropdown`
- для выбора участников
- `ContextMenu`
- только как дополнительное context-menu по `parentRef`
- не как основной visible dropdown у кнопки `...`
## Минимальный check-list перед merge
Перед завершением задачи с dropdown надо проверить:
- popup открывается по клику на весь intended trigger
- popup не клипается родительским контейнером
- popup не уезжает за экран
- popup не живет на отдельном локальном menu-engine без причины
- `Статус / Приоритет / ...` на карточке не конфликтуют по click-handling
- карточка не перехватывает клик у trigger
- popup визуально соответствует matte glass канону
- hover, active и selected состояния выглядят как часть одной системы
## Канон внедрения
С этого момента правило простое:
- одинаковая задача = одинаковый dropdown-engine
- одинаковый control-type = одинаковый placement-contract
- одинаковый popup-type = одинаковый visual shell
Нельзя:
- лечить оффсет одного окна отдельной заплаткой
- оставлять соседние dropdown на разных механиках
- дублировать menu-логику в карточке, detail-pane и sidebar
Нужно:
- расширять shared-компонент
- переиспользовать его на всех экранах
- документировать новый вариант сразу в этом файле