Розробка кастомної теми Grav (Twig)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомної теми Grav (Twig)
Середня
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка користувацької теми Grav (Twig)

Тема Grav — це директорія в user/themes/, що містить Twig-шаблони, CSS, JS та файли конфігурації. Кожний файл контенту .md використовує шаблон з такою ж назвою: services.mdtemplates/services.html.twig. Якщо шаблон не знайдено — використовується default.html.twig.

Структура теми

user/themes/my-theme/
  templates/
    partials/
      base.html.twig        # базовий layout
      navigation.html.twig  # навігація
      sidebar.html.twig
    modular/
      hero.html.twig        # модульні секції
      features.html.twig
      testimonials.html.twig
    default.html.twig       # fallback
    home.html.twig
    blog.html.twig
    post.html.twig
    service-detail.html.twig
    error.html.twig
  css/
    theme.css
  js/
    theme.js
  images/
  blueprints/               # конфігурація полів теми для адмін-панелі
    pages/
      service-detail.yaml
  blueprints.yaml           # метадані теми
  my-theme.php              # PHP-клас теми (опціонально)
  my-theme.yaml             # налаштування теми за замовчуванням
  thumbnail.jpg

Базовий layout: base.html.twig

{# templates/partials/base.html.twig #}
<!DOCTYPE html>
<html lang="{{ grav.language.getLanguage() }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>
        {% if page.title %}{{ page.title }} | {% endif %}{{ site.title }}
    </title>

    {% if page.header.metadata.description is defined %}
        <meta name="description" content="{{ page.header.metadata.description }}">
    {% elseif page.summary %}
        <meta name="description" content="{{ page.summary|striptags|trim }}">
    {% endif %}

    {% block stylesheets %}
        {% do assets.addCss('theme://css/theme.css', 100) %}
    {% endblock %}
    {{ assets.css()|raw }}
</head>
<body class="{{ page.template }}{% if page.header.body_class %} {{ page.header.body_class }}{% endif %}">

    {% include 'partials/navigation.html.twig' %}

    {% block content %}{% endblock %}

    {% include 'partials/footer.html.twig' %}

    {% block javascripts %}
        {% do assets.addJs('theme://js/theme.js', 100) %}
    {% endblock %}
    {{ assets.js()|raw }}
</body>
</html>

Шаблон сторінки

{# templates/service-detail.html.twig #}
{% extends 'partials/base.html.twig' %}

{% block content %}
<section class="hero
    {%- if page.header.hero_image %} hero--image{% endif %}">
    {% if page.media[page.header.hero_image] is defined %}
        {% set hero = page.media[page.header.hero_image] %}
        <img src="{{ hero.url }}"
             srcset="{{ hero.resize(800,400).url }} 800w,
                     {{ hero.resize(1600,800).url }} 1600w"
             alt="{{ page.title }}">
    {% endif %}
    <div class="hero__inner">
        <h1>{{ page.title }}</h1>
        {% if page.header.intro %}<p>{{ page.header.intro }}</p>{% endif %}
    </div>
</section>

<div class="container layout-main">
    <article class="content">
        {{ page.content|raw }}

        {% if page.header.features is defined %}
        <ul class="features-list">
            {% for feature in page.header.features %}
            <li>
                <i class="icon {{ feature.icon }}"></i>
                {{ feature.text }}
            </li>
            {% endfor %}
        </ul>
        {% endif %}
    </article>

    {% if page.header.show_sidebar %}
    {% include 'partials/sidebar.html.twig' %}
    {% endif %}
</div>
{% endblock %}

Навігація з активним станом

{# templates/partials/navigation.html.twig #}
{% set tree = pages.find('/').children.visible %}
<nav class="main-nav">
    <ul>
        {% for item in tree %}
        {% set is_active = (item.active or item.activeChild) %}
        <li class="{{ is_active ? 'active' : '' }}
            {{- item.children.visible|length ? ' has-children' : '' }}">
            <a href="{{ item.url }}">{{ item.title }}</a>
            {% if item.children.visible|length %}
            <ul class="submenu">
                {% for child in item.children.visible %}
                <li class="{{ child.active ? 'active' : '' }}">
                    <a href="{{ child.url }}">{{ child.title }}</a>
                </li>
                {% endfor %}
            </ul>
            {% endif %}
        </li>
        {% endfor %}
    </ul>
</nav>

Модульні сторінки

Модульна сторінка складається з кількох .md-файлів в одній директорії:

pages/01.home/
  home.md              # тип: modular
  _hero/
    hero.md            # шаблон: modular/hero
  _features/
    features.md        # шаблон: modular/features
  _cta/
    cta.md
{# templates/home.html.twig #}
{% extends 'partials/base.html.twig' %}

{% block content %}
{% for module in page.collection %}
    {% include ['modular/' ~ module.template ~ '.html.twig',
                'modular/default.html.twig'] ignore missing with {page: module} %}
{% endfor %}
{% endblock %}
{# templates/modular/hero.html.twig #}
<section class="section-hero">
    <h1>{{ page.header.title_large ?? page.title }}</h1>
    <p>{{ page.header.subtitle }}</p>
    {% if page.header.cta_url %}
    <a href="{{ page.header.cta_url }}" class="btn btn-primary">
        {{ page.header.cta_text ?? 'Дізнатися більше' }}
    </a>
    {% endif %}
</section>

Робота з медіафайлами

{# Зображення з директорії сторінки #}
{% set images = page.media.images %}
{% for image in images %}
    {# Зміна розміру зі збереженням пропорцій #}
    <img src="{{ image.cropResize(400, 300).url }}"
         alt="{{ image.attribute('alt') }}"
         loading="lazy">
{% endfor %}

{# Конкретне зображення #}
{% set img = page.media['hero.jpg'] %}
{% if img %}
    <picture>
        <source srcset="{{ img.format('webp').resize(1200, 0).url }}" type="image/webp">
        <img src="{{ img.resize(1200, 0).url }}" alt="{{ page.title }}">
    </picture>
{% endif %}

my-theme.yaml — Конфігурація теми

# user/themes/my-theme/my-theme.yaml
enabled: true
favicon: images/favicon.png
google_analytics_id: ''
show_breadcrumbs: true
sidebar_position: right   # left, right, none
posts_per_page: 10
social:
  twitter: ''
  telegram: ''

У шаблоні: {{ theme_config.google_analytics_id }}, {{ theme_config.posts_per_page }}.

Twig-розширення Grav

Grav додає глобальні об'єкти та фільтри:

{# Глобальні об'єкти #}
{{ grav.language.getLanguage() }}     {# поточна мова #}
{{ grav.user.username }}              {# поточний користувач #}
{{ site.title }}                      {# назва сайту #}
{{ page.url }}                        {# URL сторінки #}
{{ base_url_absolute }}               {# абсолютний базовий URL #}
{{ theme_url }}                       {# URL теми #}

{# Фільтри #}
{{ 'hello world'|t }}                 {# переклад #}
{{ somevar|defined('default') }}      {# значення за замовчуванням #}
{{ page.date|date('d.m.Y') }}

{# Функції #}
{{ url('theme://images/logo.png') }}
{{ random(['a', 'b', 'c']) }}

Терміни розробки

Тема Склад Термін
Базова тема з макету 5–8 шаблонів, без модульних 1–2 тижні
Повна тема з модульними 8–15 шаблонів, blueprints 3–5 тижнів
Багатомовна тема + переводи, мовні меню +3–5 днів