Меню
На этой странице
Меню — это <details> с оформленной панелью. Это сознательный выбор в пользу него, а не popover API: popover живёт в верхнем слое, и без anchor positioning его нельзя расположить относительно своего триггера, а этой возможности пока есть не везде. <details> располагается правильно уже сегодня, и загружать для него ничего не надо.
Триггер — это тот самый <summary>, оформленный под кнопку, поэтому подпись и пропсы кнопки вы передаёте в menu(), а не подсовываете готовый button(). Summary уже интерактивен, и кнопка внутри него вкладывает два элемента управления туда, где действие всего одно: невалидная разметка и две остановки табуляции на одну вещь.
Закрытие по клику снаружи и по Escape приходит из обработчика ontoggle, который подгружает их при первом открытии меню — и только тогда. Если этот модуль так и не придёт, меню всё равно открывается и закрывается собственным summary.
Простое меню
menu({ trigger: 'Действия' },
menuItem({ href: '#edit' }, 'Изменить'),
menuItem({ href: '#duplicate' }, 'Дублировать'),
menuSeparator(),
menuItem({ href: '#delete' }, 'Удалить'),
)Выравнивание
Меню раскрывается от начального края триггера. align: 'end' переворачивает это — как раз то, что нужно меню у правого края панели.
stack({ direction: 'row', gap: 'xl', justify: 'space-between', style: 'width: 100%' },
menu({ trigger: 'По началу', variant: 'soft' },
menuItem({ href: '#a' }, 'Первый'),
menuItem({ href: '#b' }, 'Второй'),
),
menu({ trigger: 'По концу', variant: 'soft', align: 'end' },
menuItem({ href: '#c' }, 'Первый'),
menuItem({ href: '#d' }, 'Второй'),
),
)Триггеры-иконки
Иконке без текста в trigger нужен label: он становится доступным именем, которого иконка дать не может.
stack({ direction: 'row', gap: 'md' },
menu({
align: 'end',
label: 'Ещё действия',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '#rename' }, 'Переименовать'),
menuItem({ href: '#move' }, 'Переместить'),
menuSeparator(),
menuItem({ href: '#archive' }, 'В архив'),
),
)Пункты с иконками
menu({ trigger: 'Файл' },
menuItem({
href: '#new',
icon: icon('plus'),
}, 'Новая страница'),
menuItem({
href: '#open',
icon: icon('folder'),
}, 'Открыть…'),
menuSeparator(),
menuItem({
href: '#build',
icon: icon('zap'),
}, 'Собрать сайт'),
)Кнопки вместо ссылок
Пункт без href рисует <button> — для действия, которое происходит на странице, а не для перехода.
menu({ trigger: 'Экспорт', variant: 'soft', color: 'primary' },
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Экспортировано в JSON.',{color:'success'}))" }, 'В JSON'),
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Экспортировано в CSV.',{color:'success'}))" }, 'В CSV'),
)В панели приложения
appBar({ brand: 'sitelo' },
appBarSpacer(),
appBarActions(
themeToggle(),
menu({
align: 'end',
label: 'Ещё',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '/ru/docs' }, 'Документация'),
menuItem({ href: '/ru/examples' }, 'Примеры'),
menuSeparator(),
menuItem({ href: 'https://github.com/paul-browne/sitelo' }, 'GitHub'),
),
),
)Доступность
Панель имеет role="menu", её пункты — role="menuitem", а summary несёт aria-haspopup. <details> не является нативным меню-виджетом, так что это разумное приближение, а не идеал: для обычного списка ссылок nav внутри details ничуть не хуже и обещает меньше.
Пропсы
menu() — пропсы триггера те же, что у кнопки:
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
trigger | Child | — | Видимая подпись. Передавайте текст, а не готовый button(). |
icon | Child | — | Разметка перед подписью или сама по себе — для триггера из одной иконки. |
label | string | — | Доступное имя. Обязателен, когда есть иконка и нет текста trigger. |
variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'outline' | Оформление триггера. |
color | 'primary' | 'neutral' | 'success' | 'warning' | 'danger' | 'neutral' | Из какой палитры берёт цвета триггер. |
size | 'sm' | 'md' | 'lg' | 'md' | Размер триггера. |
align | 'start' | 'end' | 'start' | По какому краю триггера выравнивается панель. |
triggerClass | string | — | Дополнительные классы для триггера, а не для обёртки details. |
menuItem():
| Проп | Тип | По умолчанию | Описание |
|---|---|---|---|
href | string | — | Рисует ссылку; без него — кнопку. |
icon | Child | — | Разметка перед подписью. |
as | string | 'button' | Какой элемент рендерить, когда href нет. |
menuSeparator() пропсов не принимает — это тонкая линия между группами пунктов.