Перейти до вмісту
tatetUI

Menu

Випадне меню, телепортоване в body, з фліпом і притисканням до вікна.

Меню телепортується в body, а не рендериться в потоці. Це не оптимізація: всередині контейнера з overflow: hidden або overflow-x: auto — а це будь-яка таблиця — випадайка в потоці була б обрізана краєм контейнера.

Приклад

Обрано: —

API

Props

НазваТипТиповоОпис
ariaLabelstringundefinedДоступна назва панелі, особливо для `panelRole="dialog"`.
placementbottom-endbottom-startbottomtopright"bottom-end"Куди відкривати відносно тригера. Автоматично фліпається, якщо не влазить.
panelRolenonemenudialog"menu"Семантика панелі: menu для команд, dialog для складених контролів.
widthstring—Ширина панелі, напр. `"14rem"`. Без неї — за вмістом.
disabledboolean——

Події

НазваPayloadОпис
update:open[value: boolean]Меню відкрито або закрито.

Слоти

НазваОпис
triggerТригер. `triggerAttrs` потрібно передати реальній кнопці через `v-bind`, а `toggle` — у `@click` без обгортки: з події він дізнається, що меню відкрили з клавіатури, і ставить фокус на перший пункт.
contentВміст панелі. `toggle` дає змогу закрити меню з пункту.

Доступно через ref

НазваТипОпис
open(focus?: false | "first" | "last") => Promise<void>Відкрити меню програмно; `'first'`/`'last'` переносить фокус на пункт.
close(restoreFocus?: boolean) => voidЗакрити меню програмно.

Коли використовувати

Для набору дій над однією сутністю, які не варті окремих кнопок: перейменувати, дублювати, експортувати, видалити.

Тригер малюєте самі через слот — компонент не нав'язує вигляду кнопки. Передайте triggerAttrs реальній кнопці через v-bind, щоб ARIA-стан належав фокусованому елементу, а не wrapper-у, а toggle — прямо в @click="toggle", без обгортки: з події він дізнається, що меню відкрили клавіатурою.

Коли НЕ використовувати

Не робіть меню з вибором значення. Для цього є UiSelect — він має role="combobox", працює з клавіатури і повідомляє обране значення.

Не ховайте в меню головну дію екрана. Те, що потрібно всім, має бути кнопкою.

Доступність

Тригер отримує aria-haspopup і aria-expanded через triggerAttrs, панель — role="menu". Пунктам додавайте role="menuitem" самі: компонент не знає, чим ви їх наповните.

Enter і Space на тригері відкривають меню й ставлять фокус на перший пункт, ArrowDown/ArrowUp — на перший чи останній. Відкрите мишею меню фокус не перехоплює.

Стрілки, Home та End переміщують фокус між пунктами, а друк літери — на пункт, що з неї починається (typeahead): буфер живе пів секунди, та сама літера поспіль перебирає пункти на неї.

Tab закриває меню й веде до елемента за тригером на сторінці, Shift+Tab — на сам тригер: панель стоїть у кінці body, і нативний Tab вивів би фокус за межі документа. Для складеного вмісту на кшталт налаштувань таблиці використовуйте panelRole="dialog" і ariaLabel: тоді Tab ходить контролами панелі й виходить лише з останнього.

Після закриття фокус повертається на тригер. Без цього Tab починав би обхід з початку сторінки, і користувач втрачав би місце.

Escape і клік «повз» ідуть через спільний стек оверлеїв: Escape отримує лише верхній шар (діалог, відкритий із пункту, закривається раніше за меню), а меню в модалці закривається від кліку деінде в тій самій модалці.

Деталі реалізації

Позиція рахується спільною геометрією з utils/overlayPosition, без @floating-ui — компонент має копіюватись у чужий проєкт без нової залежності. top біля верхнього краю перевертається вниз, right біля правого — ліворуч, з вирівняними верхніми краями.

Меню, вище за вільне місце у вікні, отримує max-height і прокрутку — лише тоді, коли справді не влазить: постійний overflow обрізав би кільця фокуса пунктів.

Вимірювання відбувається двічі: перший раз із запасними габаритами (панелі ще немає в DOM), другий — коли вона їх отримала. Без другого проходу вибір напрямку відкриття інколи помилявся.

Слухач прокрутки навішено з capture: true. Подія scroll не спливає, тож без цього відкрита панель лишалася б висіти на місці, поки контейнер під нею від'їжджає.

Базовий z-index панелі — 1100; усередині глибоко вкладеного оверлея він автоматично піднімається над шаром найближчого Modal або Drawer.