# Компоненты: шапка, меню и подвал

Все главы  [1 Перед началом](https://eile.site/cookbook/nachalo)[2 Установка](https://eile.site/cookbook/ustanovka)[3 Страницы](https://eile.site/cookbook/stranitsy)[4 Первый шаблон](https://eile.site/cookbook/pervyi-shablon)[5 Компоненты](https://eile.site/cookbook/komponenty)[6 Рамка сайта](https://eile.site/cookbook/ramka)[7 Поля страниц](https://eile.site/cookbook/polya)[8 Списки страниц](https://eile.site/cookbook/spiski)[9 Свои настройки](https://eile.site/cookbook/nastroiki)[10 Заявка на замер](https://eile.site/cookbook/forma)[11 Живые части](https://eile.site/cookbook/zhivye-chasti)[12 В интернет](https://eile.site/cookbook/v-internet)[13 Что дальше](https://eile.site/cookbook/dalshe)

# Компоненты: шапка, меню и подвал

Шапку и подвал удобно держать отдельными кусочками и подключать в любой шаблон. Такие кусочки называются компонентами. Сделаем три: меню, шапку и подвал.

Глава 5 из 13 · 4 минуты чтения

## Зачем нужны компоненты

Скоро у сайта появятся другие шаблоны: для кухонь, для каталога, для проектов. Шапка и подвал нужны везде. Если копировать их в каждый шаблон, то, чтобы поменять телефон в подвале, придётся править пять мест и не забыть ни одно.

Компонент решает это: шапка пишется один раз и подключается одной строкой. Поменяли компонент, и все шаблоны получили новую шапку.

## Меню из дерева

Начнём с меню. Оно должно показывать страницы верхнего уровня и само обновляться, когда вы добавляете страницу. Создайте компонент: на вкладке **Элементы** нажмите **+** рядом со словом «Компоненты» и напишите имя `Nav`.

Компонент Nav

```
<!-- @component
Меню сайта: страницы верхнего уровня.
-->
<script>
  import { children, current, link } from '$core';

  const here = current();
  const items = children(null);
</script>

<nav class="nav">
  {#each items as item (item.id)}
    <a
      href={link(item.id)}
      aria-current={here?.id === item.id ? 'page' : undefined}
    >
      {item.menuTitle || item.title}
    </a>
  {/each}
</nav>

<style>
  .nav {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.25rem;
  }

  .nav a {
    color: inherit;
    text-decoration: none;
  }

  .nav a[aria-current] {
    font-weight: 700;
  }
</style>
```

Что здесь происходит:

- `<!-- @component … -->`: описание компонента. Менеджер покажет его рядом с именем, а вы через месяц вспомните, зачем он нужен.
- `import { children, current, link } from '$core';`: берём из `$core` три функции. `$core` это модуль, через который шаблоны и компоненты получают данные сайта.
- `children(null)`: страницы верхнего уровня, в том порядке, как они стоят в дереве. Вместо `null` можно передать id страницы, и получите её дочерние.
- `link(item.id)`: адрес страницы по её номеру. Переименуете страницу, и ссылка поменяется сама.
- `current()`: страница, которую сейчас смотрит посетитель. Сравнив номера, мы помечаем текущий пункт меню, и он становится жирным.
- `(item.id)` после `as item`: ключ пункта списка. Svelte по нему отличает пункты друг от друга.

## Шапка

Новый компонент `Header`:

Компонент Header

```
<!-- @component
Шапка: название сайта и меню.
-->
<script>
  import Nav from '$components/Nav';
  import { site } from '$core';
</script>

<header class="header">
  <a class="logo" href="/">{site().name}</a>
  <Nav />
</header>

<style>
  .header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    max-width: 48rem;
    margin: 0 auto;
    padding: 1.5rem;
  }

  .logo {
    color: inherit;
    font-size: 1.25rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-decoration: none;
  }
</style>
```

Компонент подключает другой компонент так же, как шаблон: `import Nav from '$components/Nav';` и дальше `<Nav />`. Название сайта берём через `site().name`: в компонент `site` не приходит параметром, его спрашивают у `$core`.

## Подвал с параметром

У подвала будет параметр: строка рядом с названием. Так один компонент может выглядеть по-разному в разных шаблонах.

Компонент Footer

```
<!-- @component
Подвал: название, строка о студии и год.
-->
<script>
  import { site } from '$core';

  let {
    /** Строка рядом с названием */
    note = 'кухни на заказ',
  } = $props();

  const year = new Date().getFullYear();
</script>

<footer class="footer">
  <p><b>{site().name}</b> · {note}</p>
  <p>© {year}</p>
</footer>

<style>
  .footer {
    max-width: 48rem;
    margin: 0 auto;
    padding: 1.5rem;
    color: #6b6862;
    font-size: 0.9rem;
  }
</style>
```

Параметры компонент получает через `$props()`, как шаблон получает `page`. После знака `=` стоит значение по умолчанию: если параметр не передали, подвал напишет «кухни на заказ». Комментарий `/** … */` над параметром менеджер покажет как подсказку.

## Собрать шаблон из компонентов

Теперь шаблон Base становится короче. Замените его код целиком:

Шаблон Base

```
<script>
  import Content from '$core/Content';
  import Footer from '$components/Footer';
  import Header from '$components/Header';

  let { page, site } = $props();
</script>

<svelte:head>
  <title>{page.longTitle || page.title} — {site.name}</title>
</svelte:head>

<Header />

<main>
  <h1>{page.longTitle || page.title}</h1>
  <Content of={page} />
</main>

<Footer note="кухни по вашим размерам" />

<style>
  :global(body) {
    margin: 0;
    background: #f1efeb;
    color: #1f1e1c;
    font: 18px/1.6 system-ui, sans-serif;
  }

  main {
    max-width: 48rem;
    margin: 0 auto;
    padding: 0 1.5rem 2rem;
  }

  h1 {
    font-size: 2.75rem;
    line-height: 1.05;
    letter-spacing: -0.02em;
  }
</style>
```

Подвалу мы передали свою строку: `<Footer note="кухни по вашим размерам" />`. Параметры пишутся как атрибуты тега.

![Шапка с меню и подвал на сайте студии](https://eile.site/media/screens/cook-components-light.webp?v=f3bf46800c5b1177)

Шапка с меню и подвал на сайте студии