Вертикальное меню и управление экземпляром
Откройте и закройте пункт кнопками, включите запрет открытия и посмотрите журнал. Callback afterClick относится к ссылкам с aria-expanded, а не ко всем ссылкам меню.
Ссылки ведут к этому блоку примера.
HTML
<div class="nav-demo-actions">
<button id="nav-api-open" type="button" class="btn btn-primary btn-sm">Открыть через API</button>
<button id="nav-api-close" type="button" class="btn btn-surface btn-sm">Закрыть через API</button>
<label><input id="nav-api-block" type="checkbox"> Запретить открытие</label>
</div>
<nav id="nav-demo-api" class="vg-nav nav-demo nav-demo--dropdown" data-nav-demo aria-label="Вертикальное меню">
<ul class="vg-nav-wrapper">
<li id="nav-api-dropdown" class="dropdown">
<a href="#nav-api-result">Управление</a>
<div class="dropdown-content"><ul class="vg-nav-drop-list">
<li><a href="#nav-api-result">Пользователи</a></li>
<li><a href="#nav-api-result">Права доступа</a></li>
</ul></div>
</li>
<li><a href="#nav-api-result">Обзор</a></li>
</ul>
</nav>
<p id="nav-api-result" class="nav-demo-result">Ссылки ведут к этому блоку примера.</p>
<pre id="nav-api-log" class="nav-demo-result nav-demo-log" aria-live="polite" aria-label="Журнал событий"></pre>
JavaScript
/**
* Описание: рабочий пример VGNav — api.
* Возможности: демонстрирует методы, callbacks и отменяемые события.
*/
import { VGNav } from 'vgapp';
function initNavDemo() {
const root = document.querySelector('#nav-demo-api');
if (!root || VGNav.getInstance(root)) return;
const log = document.querySelector('#nav-api-log');
const blocked = document.querySelector('#nav-api-block');
const dropdown = document.querySelector('#nav-api-dropdown');
const entries = [];
const write = (message) => {
entries.unshift(message);
log.textContent = entries.slice(0, 12).join('\n');
};
['vg.nav.show', 'vg.nav.shown', 'vg.nav.hide', 'vg.nav.hidden'].forEach((name) => {
root.addEventListener(name, (event) => {
if (name === 'vg.nav.show' && blocked.checked) {
event.preventDefault();
write(name + ' → отменено');
return;
}
write(name);
});
});
VGNav.init(root, {
breakpoint: false,
placement: 'vertical',
hover: false,
hamburger: { enable: false },
callbacks: {
afterInit: () => write('afterInit'),
afterClick: (instance, event, link) => write('afterClick: ' + link.textContent.trim())
}
});
const instance = VGNav.getInstance(root);
document.querySelector('#nav-api-open').addEventListener('click', (event) => {
// Это внешние кнопки API: не передаём их клик глобальному закрытию снаружи.
event.stopPropagation();
if (!dropdown.classList.contains('active')) instance.show({ relatedTarget: dropdown });
});
document.querySelector('#nav-api-close').addEventListener('click', (event) => {
event.stopPropagation();
instance.hide({ relatedTarget: dropdown });
});
}
initNavDemo();
CSS
/* Композиция примеров; поведение dropdown остаётся в VGNav. Токены — OKAUX. */
.vg-nav.nav-demo {
--vg-nav-item-padding: .75rem 1rem;
--vg-nav-item-white-space: normal;
--vg-nav-drop-width: min(16rem, calc(100vw - 3rem));
--vg-nav-drop-border-radius: .75rem;
--vg-nav-drop-item-bg-hover: var(--vg-background-tertiary);
--vg-nav-drop-item-color-hover: var(--vg-text-primary);
--vg-nav-drop-mega-min-height: 0;
--vg-nav-drop-mega-border-radius: .75rem;
--vg-nav-drop-mega-width: min(30rem, calc(100vw - 3rem));
min-width: 0;
}
.nav-demo > .vg-nav-wrapper {
flex-wrap: wrap;
}
.nav-demo.vg-nav-overflow-enabled > .vg-nav-wrapper {
flex-wrap: nowrap;
}
.nav-demo.vg-nav-overflow-enabled {
--vg-nav-item-white-space: nowrap;
}
.nav-demo-overflow-frame {
width: 100%;
max-width: 100%;
transition: width .25s ease;
}
.nav-demo-overflow-frame.is-narrow {
width: min(22rem, 100%);
}
.nav-demo-overflow-frame .vg-nav {
width: 100%;
}
/* Резерв места под dropdown, чтобы он не перекрывал кнопку раскрытия кода. */
.nav-demo--dropdown {
min-height: 14rem;
}
.vg-nav.nav-demo--directions {
--vg-nav-drop-width: min(13rem, calc(100vw - 3rem));
min-height: 22rem;
}
.nav-demo--directions > .vg-nav-wrapper {
justify-content: flex-end;
}
.nav-demo-directions {
white-space: pre-line;
}
@media (max-width: 600px) {
.vg-nav.nav-demo--directions {
min-height: 30rem;
}
}
.nav-demo .vg-nav-drop-list {
margin: 0;
padding: 0;
list-style: none;
}
.nav-demo--scroll {
--vg-nav-drop-max-height: 12rem;
min-height: 17rem;
}
.nav-demo-mega-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
gap: 1rem;
}
.nav-demo-mega-grid a {
display: block;
padding: .5rem 0;
}
.nav-demo-result {
display: block;
margin-top: 1rem;
padding: .75rem;
border: 1px solid var(--vg-border-default);
border-radius: .5rem;
background: var(--vg-background-secondary);
color: var(--vg-text-primary);
overflow-wrap: anywhere;
}
.nav-demo-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .75rem;
margin-bottom: 1rem;
}
.nav-demo-sidebar {
--vg-sidebar-horizontal-width: min(22rem, 100vw);
--vg-sidebar-padding: 1.25rem;
}
.nav-demo-log {
max-height: 12rem;
overflow-y: auto;
white-space: pre-wrap;
}