플로팅 UI
인터랙티브 요소가 트리거를 기준으로 위치해야 하는 모든 경우 @floating-ui/dom을 사용합니다. 드롭다운, 팝오버, 메뉴, 콤보박스 리스트, 툴팁에 대해 수동으로 좌표를 계산하거나 CSS 절대 오프셋에 의존하지 마십시오.
필수 구조
- 오버레이는
createPortal(..., document.body)를 통해 렌더링하여 레이아웃 및 쌓임 맥락이 클리핑되지 않도록 합니다. autoUpdate내부에서computePosition으로 위치를 지정합니다. 오버레이가 닫히거나 언마운트될 때autoUpdate가 반환한 함수를 정리합니다.apps/desktop/src/components/ui에 존재하는 가장 가까운 컨트롤에서 시작합니다. 인터랙션이 의도적으로 차이가 필요한 경우가 아니라면 해당 컨트롤의placement,offset,flip,shift,size미들웨어를 일치시킵니다.- 계산된
left,top, 너비, 사용 가능한 높이를 React 상태에 저장합니다. 포털 루트에 상태를 적용합니다. 요소 스타일을 직접 변경하지 마십시오. - 참조 너비나 뷰포트 높이가 오버레이를 제한할 때
size를 사용합니다. 커질 수 있는 리스트는components/virtual/VirtualList.tsx를 사용해야 합니다. 고정된 헤더와 푸터는 가상 뷰포트 외부에 유지합니다.
상호작용 불변 규칙
- 포털 오버레이 내부에 포커스가 있는 동안 트리거의 열림/포커스 테두리가 보이도록 유지합니다.
- 외부 포인터 상호작용 및 Escape로 닫은 후 포커스를 트리거로 되돌립니다.
- 외부 클릭 확인에는 트리거와 오버레이를 모두 포함해야 합니다. 중첩된 포털 오버레이는 부모의 외부 클릭 리스너에 도달하는 포인터 이벤트를 막아야 하므로, 하위 메뉴와의 상호작용이 부모를 닫지 않습니다.
- 트리거 상태를
aria-expanded,aria-controls, 적절한aria-haspopup으로 노출합니다. 오버레이에 일치하는 menu, listbox, dialog, tooltip 시맨틱을 부여합니다. - 모든 뷰포트 가장자리 근처 배치, 중첩 오버레이 클릭, 키보드 해제, 가상 스크롤, 고정 푸터 동작을 확인합니다.