# 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-компонент - переиспользовать его на всех экранах - документировать новый вариант сразу в этом файле