NODEDC_TASKMANAGER/HDROPDOWN-CANON.md

18 KiB
Raw Blame History

HDROPDOWN CANON

Документ фиксирует единый канон dropdown-окон NODE.DC.

Цель документа:

  • убрать локальные самодельные реализации выпадающих окон
  • перевести dropdown на переиспользуемые shared-компоненты
  • зафиксировать единый контракт для trigger, portal, placement, стилей и поведения
  • исключить повторение проблемы, когда соседние controls работают по-разному на одном и том же экране

Базовый принцип

Dropdown в системе не является "локальной маленькой версткой рядом с кнопкой".

Dropdown это:

  • отдельный floating-layer
  • отдельный reusable UI-компонент
  • предсказуемый контракт открытия и закрытия
  • единый визуальный канон

Если на экране есть несколько выпадающих окон, пользователь не должен видеть разные механики:

  • одно окно открывается по тексту
  • второе только по стрелке
  • третье клипается контейнером
  • четвертое открывается с другим типом привязки

Это считается дефектом стандартизации.

Какие dropdown бывают

В проекте фиксируются три типа выпадающих окон.

1. Selection dropdown

Используется там, где пользователь выбирает значение.

Примеры:

  • Статус
  • Приоритет
  • Дата
  • Назначенные
  • Метки
  • выбор проекта
  • выбор модуля

Смысл:

  • пользователь выбирает одно или несколько значений
  • у dropdown есть список опций
  • часто есть поиск
  • текущее значение отображается в trigger

Канонические reference-реализации:

2. Action dropdown

Используется там, где пользователь вызывает набор действий.

Примеры:

  • ... на карточке задачи
  • ... в detail-header
  • быстрые действия у рабочего элемента

Смысл:

  • dropdown не меняет поле напрямую
  • dropdown показывает список команд
  • каждая строка запускает action

Канонический shared-компонент:

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 текущий канон зафиксирован в:

Для selection dropdown reference-стек уже зафиксирован в:

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:

Смысл:

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