Добавлена нижняя навигационная панель для мобильных устройств

- Добавлена bottom navbar с 5 кнопками (Главная, Уведомления, Поиск, Календарь, Ещё)
- Панель отображается только на мобильных устройствах (до 768px)
- Использованы системные CSS переменные и стили
- Удалена кнопка гамбургера из верхней панели (дублирование функционала)
- Добавлена синхронизация бейджей уведомлений
- Поддержка светлой и темной темы
- Backdrop-filter для полупрозрачности в iOS-стиле
This commit is contained in:
Игорь Надеин 2026-01-12 08:32:16 +03:00
parent 51e4b63def
commit 003bbab1ff
3 changed files with 245 additions and 38 deletions

View File

@ -83,3 +83,117 @@
[data-bs-theme="dark"] .mobile-search-overlay {
background-color: rgba(0, 0, 0, 0.7);
}
/* ─────────────────────────────────────────────────────────────
BOTTOM NAVBAR (только для мобильных)
────────────────────────────────────────────────────────────── */
.bottom-navbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: var(--bs-primary);
border-top: 0.5px solid var(--bs-border-color);
z-index: 1040;
padding: 0;
box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.08);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
/* Темная тема */
[data-bs-theme="dark"] .bottom-navbar {
background-color: var(--bs-tertiary-bg);
border-top-color: var(--bs-border-color);
box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.3);
}
.bottom-navbar-container {
display: flex;
justify-content: space-around;
align-items: center;
height: 60px;
padding: 0 8px;
max-width: 100%;
}
.bottom-nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
color: rgba(255, 255, 255, 0.7);
text-decoration: none;
border: none;
background: transparent;
padding: 4px 8px;
transition: all 0.2s ease;
position: relative;
cursor: pointer;
}
/* Темная тема - другой цвет кнопок */
[data-bs-theme="dark"] .bottom-nav-item {
color: var(--bs-secondary-color);
}
.bottom-nav-item:hover,
.bottom-nav-item:focus,
.bottom-nav-item:active {
color: #fff;
background: rgba(255, 255, 255, 0.1);
outline: none;
}
[data-bs-theme="dark"] .bottom-nav-item:hover,
[data-bs-theme="dark"] .bottom-nav-item:focus,
[data-bs-theme="dark"] .bottom-nav-item:active {
color: var(--bs-emphasis-color);
background: var(--bs-secondary-bg);
}
.bottom-nav-item i {
font-size: 1.4rem;
margin-bottom: 2px;
line-height: 1;
}
.bottom-nav-label {
font-size: 0.65rem;
font-weight: 500;
text-align: center;
line-height: 1;
margin-top: 2px;
}
/* Иконка с бейджем для уведомлений */
.bottom-nav-icon-wrapper {
position: relative;
display: inline-block;
}
.bottom-nav-badge {
position: absolute;
top: -4px;
right: -8px;
background: var(--bs-danger);
color: white;
border-radius: var(--badge-radius);
padding: 2px 5px;
font-size: 0.65rem;
font-weight: 600;
min-width: 18px;
text-align: center;
line-height: 1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
/* Padding для body, чтобы контент не перекрывался нижней панелью */
@media (max-width: 767.98px) {
body {
padding-bottom: 60px;
}
}

View File

@ -0,0 +1,81 @@
/**
* Bottom Navigation Bar
* Управление нижней панелью навигации для мобильных устройств
*/
document.addEventListener('DOMContentLoaded', function() {
// Кнопка уведомлений в нижней панели
const bottomNotificationToggle = document.getElementById('bottomNotificationToggle');
const notificationDropdown = document.getElementById('notificationDropdown');
const notificationBadge = document.getElementById('notificationBadge');
const bottomNotificationBadge = document.getElementById('bottomNotificationBadge');
// Синхронизация бейджей между верхней и нижней панелью
function syncNotificationBadges() {
if (notificationBadge && bottomNotificationBadge) {
const count = notificationBadge.textContent;
const hasNotifications = !notificationBadge.classList.contains('d-none');
bottomNotificationBadge.textContent = count;
if (hasNotifications) {
bottomNotificationBadge.classList.remove('d-none');
} else {
bottomNotificationBadge.classList.add('d-none');
}
}
}
// Наблюдатель за изменениями бейджа уведомлений
if (notificationBadge) {
const observer = new MutationObserver(syncNotificationBadges);
observer.observe(notificationBadge, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
// Первоначальная синхронизация
syncNotificationBadges();
}
// Открытие уведомлений из нижней панели
if (bottomNotificationToggle && notificationDropdown) {
bottomNotificationToggle.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const isVisible = notificationDropdown.style.display !== 'none';
if (isVisible) {
notificationDropdown.style.display = 'none';
} else {
notificationDropdown.style.display = 'block';
// Позиционируем над нижней панелью
notificationDropdown.style.bottom = '70px';
notificationDropdown.style.right = '10px';
notificationDropdown.style.top = 'auto';
notificationDropdown.style.left = 'auto';
}
});
}
// Кнопка поиска в нижней панели
const bottomSearchToggle = document.getElementById('bottomSearchToggle');
if (bottomSearchToggle) {
bottomSearchToggle.addEventListener('click', function(e) {
e.preventDefault();
// Перенаправляем на страницу поиска
window.location.href = '/search/';
});
}
// Закрытие выпадающих элементов при клике вне их
document.addEventListener('click', function(e) {
if (notificationDropdown &&
!notificationDropdown.contains(e.target) &&
!bottomNotificationToggle?.contains(e.target)) {
notificationDropdown.style.display = 'none';
}
});
});

View File

@ -1,19 +1,11 @@
{% load static %}
<link rel="stylesheet" href="{% static 'css/components/navbar.css' %}">
<script src="{% static 'js/navbar-mobile-search.js' %}" defer></script>
<script src="{% static 'js/bottom-navbar.js' %}" defer></script>
<nav class="navbar navbar-expand bg-primary app-navbar" data-bs-theme="dark">
<div class="container-fluid align-items-center">
{% if user.is_authenticated %}
<!-- Моб. меню (иконка без подложки) -->
<button class="menu-toggle d-lg-none me-2 navbar-icon" type="button"
data-bs-toggle="offcanvas" data-bs-target="#sidebar" aria-controls="sidebar"
aria-label="Открыть меню">
<i class="bi-list fs-4"></i>
</button>
{% endif %}
{% load static %}
<a class="navbar-brand d-flex align-items-center gap-2 me-3 navbar-brand-item" href="/">
<img src="{% static BRAND_LOGO %}"
@ -28,23 +20,10 @@
<!-- Группа: поиск + колокол -->
<div class="d-flex align-items-center ms-4 gap-3">
<!-- Поиск: иконка на мобильном, поле на десктопе -->
<form class="d-flex align-items-center mobile-search-form" role="search"
<form class="d-none d-md-flex align-items-center mobile-search-form" role="search"
action="{% url 'search:search' %}" method="get" autocomplete="off">
<!-- Мобильная версия: иконка или поле -->
<button type="button" class="btn btn-link text-white p-0 d-md-none mobile-search-toggle" aria-label="Поиск">
<i class="bi-search fs-5"></i>
</button>
<div class="mobile-search-input d-none align-items-center gap-2">
<input
class="form-control form-control-sm rounded-pill bg-white bg-opacity-10 border-0 text-white shadow-sm"
type="search" name="q" placeholder="Поиск…" aria-label="Поиск" id="mobileSearchInput"
style="width: 200px; height: 36px; padding-left: 12px;">
<button type="submit" class="btn btn-link text-white p-0" aria-label="Найти">
<i class="bi-arrow-right-circle fs-5"></i>
</button>
</div>
<!-- Десктопная версия: поле -->
<div class="position-relative ios-search d-none d-md-block" style="max-width: clamp(160px, 28vw, 300px);">
<div class="position-relative ios-search" style="max-width: clamp(160px, 28vw, 300px);">
<i class="bi-search"></i>
<input
class="form-control form-control-sm rounded-pill ios-search-input bg-white bg-opacity-10 border-0 text-white shadow-sm"
@ -53,8 +32,8 @@
</div>
</form>
<!-- Уведомления: только иконка + плотный бейдж -->
<div class="position-relative navbar-icon" id="notificationBell">
<!-- Уведомления: только иконка + плотный бейдж (скрыть на мобильных) -->
<div class="position-relative navbar-icon d-none d-md-block" id="notificationBell">
<button class="btn btn-link text-white p-0"
id="notificationToggle"
type="button"
@ -67,18 +46,8 @@
</button>
</div>
<!-- Календарь: только на мобильных устройствах -->
<div class="position-relative navbar-icon d-lg-none">
<button class="btn btn-link text-white p-0"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#rightbarOffcanvas"
aria-controls="rightbarOffcanvas"
title="Календарь"
aria-label="Календарь">
<i class="bi-calendar-event fs-5"></i>
</button>
</div>
<!-- Календарь: только на мобильных устройствах (перенесен в bottom navbar) -->
<!-- Удалено, теперь в нижней панели -->
</div>
{% endif %}
@ -145,4 +114,47 @@
</a>
</div>
</div>
<!-- Bottom Navigation Bar (только для мобильных) -->
<nav class="bottom-navbar d-md-none" id="bottomNav">
<div class="bottom-navbar-container">
<a href="/" class="bottom-nav-item" title="Главная" aria-label="Главная">
<i class="bi-house-door"></i>
<span class="bottom-nav-label">Главная</span>
</a>
<button class="bottom-nav-item" id="bottomNotificationToggle" type="button" title="Уведомления" aria-label="Уведомления">
<span class="bottom-nav-icon-wrapper">
<i class="bi-bell"></i>
<span class="bottom-nav-badge d-none" id="bottomNotificationBadge">0</span>
</span>
<span class="bottom-nav-label">Уведомления</span>
</button>
<button class="bottom-nav-item" id="bottomSearchToggle" type="button" title="Поиск" aria-label="Поиск">
<i class="bi-search"></i>
<span class="bottom-nav-label">Поиск</span>
</button>
<button class="bottom-nav-item" type="button"
data-bs-toggle="offcanvas"
data-bs-target="#rightbarOffcanvas"
aria-controls="rightbarOffcanvas"
title="Календарь"
aria-label="Календарь">
<i class="bi-calendar-event"></i>
<span class="bottom-nav-label">Календарь</span>
</button>
<button class="bottom-nav-item" type="button"
data-bs-toggle="offcanvas"
data-bs-target="#sidebar"
aria-controls="sidebar"
title="Меню"
aria-label="Меню">
<i class="bi-three-dots"></i>
<span class="bottom-nav-label">Ещё</span>
</button>
</div>
</nav>
{% endif %}