# Списки: каталог, проекты и главная

Все главы  [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)

# Списки: каталог, проекты и главная

«Каталог» сам соберёт карточки кухонь с ценами, «Проекты» покажут работы по пять на странице, а главная станет витриной с кухнями и свежими проектами.

Глава 8 из 13 · 7 минут чтения

## Карточки кухонь

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

Шаблон Catalog

```
<script>
  import Content from '$core/Content';
  import Layout from '$components/Layout';
  import { children, image, link } from '$core';

  let { page } = $props();

  const kitchens = children(page.id);
  const rub = new Intl.NumberFormat('ru');
</script>

<Layout {page}>
  <h1>{page.title}</h1>
  <Content of={page} />

  <ul class="cards">
    {#each kitchens as kitchen (kitchen.id)}
      {@const photo = kitchen.fields.photo ? image(kitchen.fields.photo) : null}
      <li>
        <a href={link(kitchen.id)}>
          {#if photo}
            <img src={photo.src} width={photo.width} height={photo.height} alt="" />
          {/if}
          <span class="name">{kitchen.title}</span>
          <span class="meta">
            {kitchen.fields.plan} · от {rub.format(kitchen.fields.price)} ₽ за метр
          </span>
        </a>
      </li>
    {/each}
  </ul>
</Layout>

<style>
  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: 1rem;
    padding: 0;
    list-style: none;
  }

  .cards a {
    display: grid;
    gap: 0.35rem;
    padding: 0.75rem 0.75rem 1rem;
    border-radius: 1.25rem;
    background: #fff;
    color: inherit;
    text-decoration: none;
  }

  .cards img {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 0.9rem;
  }

  .name {
    font-weight: 700;
  }

  .meta {
    color: #6b6862;
    font-size: 0.9rem;
  }
</style>
```

- `children(page.id)`: дочерние страницы текущей, в порядке дерева. В пятой главе мы звали `children(null)` для верхнего уровня, здесь та же функция.
- `kitchen.fields.price`: у каждой страницы из списка есть её поля, как у `page` в шаблоне кухни.
- `{@const photo = …}`: заводит значение прямо внутри `{#each}`, своё для каждой кухни.
- `aspect-ratio: 4 / 3` и `object-fit: cover`: все фото в карточках одной высоты, даже если снимки разных пропорций.
- `grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr))`: карточки встанут в столько колонок, сколько поместится. На телефоне одна, на ноутбуке три.

Откройте страницу «Каталог» в менеджере, выберите ей шаблон Catalog и сохраните. Порядок карточек тот же, что в дереве: перетащите кухни, чтобы поменять его.

![Каталог студии: карточки кухонь с планировкой и ценой](https://eile.site/media/screens/cook-catalog-light.webp?v=847e493905443a03)

Каталог студии: карточки кухонь с планировкой и ценой

## Раздел проектов

Создайте на верхнем уровне страницу «Проекты» (адрес `proekty`), а внутри неё три работы. У каждой заполните поле **Анонс**: это одно-два предложения, которые попадут в список. Проектам оставьте шаблон Base.

Для пейджера, ссылок «Назад» и «Вперёд» между страницами списка, возьмём готовый компонент. Щёлкните в шапке значок **Элементы**, откройте вкладку **Набор компонентов** и нажмите **Установить** у компонента `Pagination`. Он появится среди компонентов сайта, и его можно будет править, как свой.

Теперь шаблон `Projects`:

Шаблон Projects

```
<script>
  import Layout from '$components/Layout';
  import Pagination from '$components/Pagination';
  import { link, listing } from '$core';

  let { page } = $props();

  const projects = listing(page.id, { perPage: 5 });
  const date = new Intl.DateTimeFormat('ru', {
    dateStyle: 'long',
    timeZone: 'UTC',
  });
</script>

<Layout {page}>
  <h1>{page.title}</h1>

  {#each projects.items as project (project.id)}
    <article class="project">
      <h2><a href={link(project.id)}>{project.title}</a></h2>
      <p class="date">{date.format(new Date(project.publishedAt))}</p>
      {#if project.introText}<p>{project.introText}</p>{/if}
    </article>
  {:else}
    <p>Проектов пока нет.</p>
  {/each}

  <Pagination
    items={projects.pager()}
    prev={projects.url(projects.number - 1)}
    next={projects.url(projects.number + 1)}
  />
</Layout>

<style>
  .project {
    padding: 1.25rem 0;
    border-bottom: 1px solid #e2dfd9;
  }

  .project h2 {
    margin: 0;
    font-size: 1.375rem;
  }

  .project a {
    color: inherit;
  }

  .date {
    margin: 0.25rem 0 0;
    color: #6b6862;
    font-size: 0.9rem;
  }
</style>
```

- `listing(page.id, { perPage: 5 })`: дочерние страницы порциями по пять. Сначала новые: последняя опубликованная работа окажется сверху.
- `projects.items`: работы текущей порции. На втором экране списка это работы с шестой по десятую.
- `{:else}` внутри `{#each}`: что показать, если список пуст.
- `projects.pager()`, `projects.url(…)`: номера страниц списка и адреса соседних. Компонент Pagination рисует из них пейджер, а при одной странице не рисует ничего.

Выберите странице «Проекты» шаблон Projects. Вторая порция откроется по адресу `/proekty/page-2`, когда работ станет больше пяти: Eile сам понимает такие адреса.

## Главная: витрина студии

Список можно собрать и из другого раздела. Главная покажет крупный заголовок, кнопку в каталог, большое фото, три кухни с ценами и два свежих проекта. Создайте шаблон `Home`:

Шаблон Home

```
<script>
  import Content from '$core/Content';
  import Layout from '$components/Layout';
  import { children, image, link, listing } from '$core';

  let { page } = $props();

  // Разделы находим по адресу: так шаблону не нужны номера страниц.
  const top = children(null);
  const catalog = top.find((_) => _.path === '/katalog');
  const projects = top.find((_) => _.path === '/proekty');
  const contacts = top.find((_) => _.path === '/kontakty');

  const kitchens = catalog ? children(catalog.id).slice(0, 3) : [];
  const fresh = projects
    ? listing(projects.id, { perPage: 2, paged: false }).items
    : [];
  const cover = kitchens[0]?.fields.photo ? image(kitchens[0].fields.photo) : null;
  const rub = new Intl.NumberFormat('ru');
</script>

<Layout {page}>
  <section class="hero">
    <h1>{page.longTitle || page.title}</h1>
    <Content of={page} />
    <p class="actions">
      {#if catalog}<a class="button" href={link(catalog.id)}>Смотреть каталог</a>{/if}
      {#if contacts}<a href={link(contacts.id)}>Записаться на замер</a>{/if}
    </p>
    {#if cover}
      <img
        src={cover.src}
        srcset={cover.srcset}
        sizes="(min-width: 48rem) 45rem, 100vw"
        width={cover.width}
        height={cover.height}
        alt={cover.alt}
      />
    {/if}
  </section>

  {#if kitchens.length}
    <h2>Из каталога</h2>
    <ul class="kitchens">
      {#each kitchens as kitchen (kitchen.id)}
        <li>
          <a href={link(kitchen.id)}>{kitchen.title}</a>
          <span>от {rub.format(kitchen.fields.price)} ₽ за метр</span>
        </li>
      {/each}
    </ul>
  {/if}

  {#if fresh.length}
    <h2>Готовые проекты</h2>
    {#each fresh as project (project.id)}
      <p class="project"><a href={link(project.id)}>{project.title}</a></p>
    {/each}
  {/if}
</Layout>

<style>
  .hero img {
    margin-top: 1.5rem;
    border-radius: 1.5rem;
  }

  .actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem;
  }

  .button {
    padding: 0.8rem 1.4rem;
    border-radius: 999px;
    background: #1f1e1c;
    color: #fff;
    text-decoration: none;
  }

  .actions a:not(.button) {
    color: inherit;
  }

  .kitchens {
    display: grid;
    gap: 0.5rem;
    padding: 0;
    list-style: none;
  }

  .kitchens li {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.25rem 1rem;
    padding: 0.9rem 1.1rem;
    border-radius: 1rem;
    background: #fff;
  }

  .kitchens span {
    color: #6b6862;
  }

  .kitchens a,
  .project a {
    color: inherit;
  }
</style>
```

- `top.find((_) => _.path === '/katalog')`: ищем среди страниц верхнего уровня ту, у которой адрес `/katalog`. Знак `_` здесь просто имя для «очередной страницы».
- `children(catalog.id).slice(0, 3)`: первые три кухни из «Каталога». Поменяете порядок в дереве, поменяются и они.
- `listing(projects.id, { perPage: 2, paged: false })`: два свежих проекта. `paged: false` говорит, что этот список не постраничный: у главной не появятся адреса вида `/page-2`.
- `kitchens[0]?.fields.photo`: фото первой кухни для обложки. Знак `?.` не даёт шаблону упасть, если кухонь в каталоге пока нет.

Выберите главной шаблон Home. Крупный заголовок возьмите из поля **Длинный заголовок** на вкладке **Поиск и соцсети**: напишите там «Кухни по вашим размерам». Он же попадёт в заголовок вкладки браузера, а в меню сайта останется короткое «Главная».

![Главная студии на шаблоне Home](https://eile.site/media/screens/cook-home-light.webp?v=3dd3f00ba8382cf0)

Главная студии на шаблоне Home