Menu

Компонент меню — фундаментальный элемент пользовательского интерфейса, обеспечивающий навигацию, доступ к действиям и структурирование контента. В экосистеме SvelteKit UI-библиотек (например, Skeleton, Flowbite-Svelte, Carbon Components Svelte) меню реализуются как высокоуровневые абстракции с акцентом на реактивность, доступность и композиционность.

Menu может выступать в нескольких ролях:

  • Навигационное меню (navbar, sidebar)
  • Контекстное меню (context menu, dropdown)
  • Меню действий (action menu)
  • Командная палитра (command palette)

Базовая структура Menu

Типичный компонент меню строится из следующих частей:

  • Trigger (инициатор) — элемент, открывающий меню
  • Content (контейнер) — выпадающая или встроенная область
  • Item (пункт меню) — отдельная интерактивная единица
  • Group (группа) — логическая группировка элементов
  • Separator (разделитель) — визуальное разделение
  • Submenu (подменю) — вложенная структура

Пример базовой реализации:

<script>
  let open = false;
</script>

<button on:click={() => open = !open}>
  Меню
</button>

{#if open}
  <ul class="menu">
    <li>Профиль</li>
    <li>Настройки</li>
    <li>Выход</li>
  </ul>
{/if}

Использование готовых UI-библиотек

Skeleton (Svelte UI Toolkit)

<script>
  import { Dropdown, DropdownItem } from '@skeletonlabs/skeleton';
</script>

<Dropdown>
  <button slot="trigger">Открыть</button>
  <DropdownItem>Профиль</DropdownItem>
  <DropdownItem>Настройки</DropdownItem>
  <DropdownItem>Выход</DropdownItem>
</Dropdown>

Особенности:

  • встроенная поддержка тем
  • удобная композиция через slots
  • адаптация под Tailwind

Flowbite-Svelte

<script>
  import { Dropdown, DropdownItem } from 'flowbite-svelte';
</script>

<Dropdown label="Меню">
  <DropdownItem>Dashboard</DropdownItem>
  <DropdownItem>Settings</DropdownItem>
  <DropdownItem>Logout</DropdownItem>
</Dropdown>

Особенности:

  • интеграция с Tailwind CSS
  • декларативный API
  • готовые стили

Реактивность и управление состоянием

Svelte позволяет управлять состоянием меню без сторонних стейт-менеджеров.

<script>
  let isOpen = false;

  function toggle() {
    isOpen = !isOpen;
  }
</script>

<button on:click={toggle}>Toggle</button>

{#if isOpen}
  <div class="menu">...</div>
{/if}

Для более сложных сценариев используется store:

import { writable } from 'svelte/store';

export const menuState = writable(false);

Обработка кликов вне компонента

Закрытие меню при клике вне области:

<script>
  import { onMount } from 'svelte';

  let open = false;
  let ref;

  function handleClickOutside(event) {
    if (ref && !ref.contains(event.target)) {
      open = false;
    }
  }

  onMount(() => {
    document.addEventListener('click', handleClickOutside);
    return () => document.removeEventListener('click', handleClickOutside);
  });
</script>

<div bind:this={ref}>
  <button on:click={() => open = !open}>Меню</button>
  {#if open}
    <div class="dropdown">...</div>
  {/if}
</div>

Доступность (Accessibility)

Качественная реализация Menu должна учитывать ARIA-атрибуты:

  • role="menu"
  • role="menuitem"
  • aria-expanded
  • aria-controls

Пример:

<button
  aria-haspopup="true"
  aria-expanded={open}
  on:click={() => open = !open}
>
  Меню
</button>

{#if open}
  <ul role="menu">
    <li role="menuitem">Пункт 1</li>
    <li role="menuitem">Пункт 2</li>
  </ul>
{/if}

Также важно:

  • поддержка клавиатуры (Tab, Arrow keys, Escape)
  • управление фокусом
  • правильная иерархия DOM

Клавиатурная навигация

Пример обработки стрелок:

<script>
  let items = [];
  let index = 0;

  function handleKey(e) {
    if (e.key === 'ArrowDown') {
      index = (index + 1) % items.length;
      items[index].focus();
    }
  }
</script>

<ul on:keydown={handleKey}>
  {#each ['One', 'Two', 'Three'] as item, i}
    <li tabindex="0" bind:this={el => items[i] = el}>
      {item}
    </li>
  {/each}
</ul>

Подменю (Nested Menus)

<li>
  Настройки
  <ul class="submenu">
    <li>Профиль</li>
    <li>Безопасность</li>
  </ul>
</li>

Реализация требует:

  • отдельного состояния для каждого уровня
  • управления hover/focus
  • задержек открытия (debounce)

Контекстное меню (Right-click)

<script>
  let visible = false;
  let x = 0, y = 0;

  function openMenu(e) {
    e.preventDefault();
    x = e.clientX;
    y = e.clientY;
    visible = true;
  }
</script>

<div on:contextmenu={openMenu}>
  Правый клик
</div>

{#if visible}
  <ul style="position: absolute; top: {y}px; left: {x}px;">
    <li>Копировать</li>
    <li>Вставить</li>
  </ul>
{/if}

Анимации

Svelte предоставляет встроенные transition-функции:

<script>
  import { fade, scale } from 'svelte/transition';
</script>

{#if open}
  <div transition:scale>
    Меню
  </div>
{/if}

Комбинации:

  • fade + scale
  • slide для боковых меню

Позиционирование

Для сложных сценариев используется Popper.js или Floating UI:

import { computePosition } from '@floating-ui/dom';

Задачи:

  • адаптация к viewport
  • предотвращение выхода за границы
  • автоматический flip

Стилизация

Меню обычно стилизуется через:

  • Tailwind CSS
  • CSS variables
  • scoped styles

Пример:

.menu {
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}

Паттерны проектирования

Compound Components

<Menu>
  <Menu.Trigger />
  <Menu.Content>
    <Menu.Item />
  </Menu.Content>
</Menu>

Render Props / Slots

<Dropdown let:open>
  <button>{open ? 'Закрыть' : 'Открыть'}</button>
</Dropdown>

Производительность

  • избегать лишних реактивных зависимостей
  • использовать lazy rendering ({#if open})
  • минимизировать DOM-узлы

Тестирование

Проверяются:

  • открытие/закрытие
  • клавиатурная навигация
  • aria-атрибуты

Инструменты:

  • Vitest
  • Testing Library

Типичные ошибки

  • отсутствие обработки клика вне меню
  • неправильная работа фокуса
  • игнорирование доступности
  • жесткая привязка к DOM

Расширенные сценарии

Mega Menu

  • многоуровневая навигация
  • сеточная структура

Command Palette

  • поиск + действия
  • управление через клавиатуру

Adaptive Menu

  • трансформация в мобильное меню (hamburger)

Интеграция с SvelteKit

Menu часто используется в:

  • +layout.svelte — глобальная навигация
  • +page.svelte — локальные действия

С учетом SSR:

  • избегать прямого обращения к window без проверки
  • использовать onMount для DOM API

Вывод архитектурных принципов

  • декларативность через Svelte syntax
  • композиция вместо монолитных компонентов
  • строгая поддержка accessibility
  • управление состоянием через реактивность
  • отделение логики от представления