feat: add fixed sidebar navigation with hover expand and mobile hamburger

Implements Task 3: fixed left sidebar (70px collapsed → 250px on hover) with
9 nav items, social links, active-state detection, and mobile hamburger toggle
that matches the existing sidebar-nav.js selectors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 01:03:57 +05:00
parent 6befaedb8f
commit b47ad7bef4
15 changed files with 409 additions and 0 deletions
@@ -52,3 +52,11 @@ foreach ( $bitwms_includes as $bitwms_file ) {
} }
} }
unset( $bitwms_file, $bitwms_path ); unset( $bitwms_file, $bitwms_path );
// ---------------------------------------------------------------------------
// 3. Inject sidebar navigation before the GeneratePress header
// ---------------------------------------------------------------------------
add_action( 'generate_before_header', function () {
get_template_part( 'templates/sidebar-nav' );
} );
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>

After

Width:  |  Height:  |  Size: 290 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>

After

Width:  |  Height:  |  Size: 369 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="23 7 16 12 23 17 23 7"/>
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
<polyline points="10 9 9 9 8 9"/>
</svg>

After

Width:  |  Height:  |  Size: 420 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>

After

Width:  |  Height:  |  Size: 328 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 281 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 10v6M2 10l10-5 10 5-10 5z"/>
<path d="M6 12v5c0 2 4 3 6 3s6-1 6-3v-5"/>
</svg>

After

Width:  |  Height:  |  Size: 280 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.58 3.43 2 2 0 0 1 3.55 1.25h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.78a16 16 0 0 0 6 6l.91-.91a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.73 16z"/>
</svg>

After

Width:  |  Height:  |  Size: 495 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"/>
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>

After

Width:  |  Height:  |  Size: 280 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<path d="M21.579 6.855c.14-.465 0-.806-.662-.806h-2.193c-.558 0-.813.295-.953.62 0 0-1.115 2.719-2.695 4.482-.512.512-.743.675-1.022.675-.14 0-.341-.163-.341-.628V6.855c0-.558-.162-.806-.626-.806H9.642c-.348 0-.557.258-.557.504 0 .528.79.65.871 2.138v3.228c0 .707-.128.836-.407.836-.743 0-2.551-2.729-3.624-5.853-.209-.607-.42-.852-.98-.852H2.752c-.627 0-.752.295-.752.62 0 .582.743 3.462 3.461 7.271 1.812 2.601 4.363 4.012 6.687 4.012 1.393 0 1.565-.313 1.565-.852v-1.966c0-.626.133-.751.574-.751.325 0 .883.163 2.183 1.417 1.487 1.487 1.731 2.152 2.567 2.152h2.192c.626 0 .939-.313.759-.931-.198-.616-.907-1.51-1.849-2.569-.512-.604-1.277-1.254-1.51-1.579-.325-.418-.232-.604 0-.976.001 0 2.672-3.761 2.95-5.042z"/>
</svg>

After

Width:  |  Height:  |  Size: 846 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</svg>

After

Width:  |  Height:  |  Size: 297 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<rect width="32" height="32" fill="#E50071"/>
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-family="Arial, sans-serif" font-weight="700" font-size="20" fill="#FFFFFF">Б</text>
</svg>

After

Width:  |  Height:  |  Size: 302 B

+200
View File
@@ -280,3 +280,203 @@ body.sidebar-overlay::after {
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
z-index: 199; z-index: 199;
} }
/* ==========================================================================
Sidebar navigation
========================================================================== */
/* Base sidebar */
.sidebar-nav {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-width-collapsed);
height: 100vh;
background: var(--color-white);
border-right: 1px solid var(--color-gray-light);
z-index: 1000;
display: flex;
flex-direction: column;
transition: width 0.25s ease;
overflow: hidden;
}
.sidebar-nav:hover {
width: var(--sidebar-width-expanded);
}
/* Logo area */
.sidebar-nav__logo {
display: flex;
align-items: center;
justify-content: center;
min-height: 60px;
border-bottom: 1px solid var(--color-gray-light);
flex-shrink: 0;
}
.sidebar-nav__logo a {
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
}
/* Menu / navigation area */
.sidebar-nav__menu {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
.sidebar-nav__list {
list-style: none;
margin: 0;
padding: 0;
}
/* Individual links */
.sidebar-nav__link {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 23px;
color: var(--color-black);
text-decoration: none;
white-space: nowrap;
transition:
background-color var(--transition-base),
color var(--transition-base);
}
.sidebar-nav__link:hover,
.sidebar-nav__link:focus {
background-color: var(--color-gray-light);
color: var(--color-black);
text-decoration: none;
}
/* Active item */
.sidebar-nav__item--active .sidebar-nav__link {
color: var(--color-primary);
font-weight: var(--font-weight-bold);
}
/* Icon */
.sidebar-nav__icon {
flex-shrink: 0;
width: 24px;
height: 24px;
}
/* Label — hidden when collapsed, visible when sidebar hovered */
.sidebar-nav__label {
opacity: 0;
transition: opacity 0.2s ease;
font-size: 0.9375rem; /* 15px */
}
.sidebar-nav:hover .sidebar-nav__label {
opacity: 1;
}
/* Social links at the bottom */
.sidebar-nav__social {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 16px;
border-top: 1px solid var(--color-gray-light);
flex-shrink: 0;
}
.sidebar-nav__social-link {
display: flex;
align-items: center;
justify-content: center;
color: var(--color-black);
opacity: 0.6;
transition: opacity var(--transition-base);
text-decoration: none;
}
.sidebar-nav__social-link:hover,
.sidebar-nav__social-link:focus {
opacity: 1;
text-decoration: none;
color: var(--color-black);
}
/* Content shift on desktop — push main content right of the sidebar */
.site-content,
.site-header,
.site-footer {
margin-left: var(--sidebar-width-collapsed);
}
/* Hamburger / toggle button */
.sidebar-nav__toggle {
display: none;
position: fixed;
top: 12px;
left: 12px;
z-index: 1100;
flex-direction: column;
gap: 4px;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
padding: 0;
background: var(--color-white);
border: 1px solid var(--color-gray-light);
cursor: pointer;
appearance: none;
-webkit-appearance: none;
}
.sidebar-nav__toggle:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
.sidebar-nav__toggle-bar {
display: block;
width: 24px;
height: 2px;
background-color: var(--color-black);
transition: transform 0.2s ease, opacity 0.2s ease;
}
/* Mobile styles */
@media (max-width: 767px) {
/* Hide sidebar off-screen by default */
.sidebar-nav {
transform: translateX(-100%);
transition: transform 0.25s ease, width 0.25s ease;
width: var(--sidebar-width-expanded);
}
/* Show sidebar when open */
.sidebar-nav--open {
transform: translateX(0);
}
/* Always show labels on mobile when open */
.sidebar-nav .sidebar-nav__label {
opacity: 1;
}
/* Show hamburger button on mobile */
.sidebar-nav__toggle {
display: flex;
}
/* Remove content margin on mobile */
.site-content,
.site-header,
.site-footer {
margin-left: 0;
}
}
@@ -0,0 +1,150 @@
<?php
/**
* БИТ.WMS Child Theme — Sidebar navigation template
*
* Fixed left sidebar: collapses to 70px (icons only), expands to 250px on hover.
* On mobile (<768px) hidden by default, toggled via .sidebar-nav__toggle button.
*
* @package bitwms-child
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$theme_uri = get_stylesheet_directory_uri();
// Current path for active-state detection.
$current_path = wp_parse_url( get_permalink(), PHP_URL_PATH );
if ( empty( $current_path ) ) {
$current_path = $_SERVER['REQUEST_URI'] ?? '/';
}
$nav_items = array(
array(
'label' => 'БИТ.WMS',
'url' => '/bit-wms/',
'icon' => 'warehouse.svg',
),
array(
'label' => 'Кейсы',
'url' => '/cases/',
'icon' => 'folder.svg',
),
array(
'label' => 'Статьи',
'url' => '/articles/',
'icon' => 'document.svg',
),
array(
'label' => 'Вебинары',
'url' => '/webinars/',
'icon' => 'camera.svg',
),
array(
'label' => 'Курсы',
'url' => '/courses/',
'icon' => 'graduation.svg',
),
array(
'label' => 'Мероприятия',
'url' => '/events/',
'icon' => 'calendar.svg',
),
array(
'label' => 'Материалы',
'url' => '/materials/',
'icon' => 'download.svg',
),
array(
'label' => 'Новости',
'url' => '/news/',
'icon' => 'bell.svg',
),
array(
'label' => 'Контакты',
'url' => '/contacts/',
'icon' => 'phone.svg',
),
);
?>
<button class="sidebar-nav__toggle" aria-label="<?php esc_attr_e( 'Открыть меню', 'bitwms-child' ); ?>" aria-expanded="false">
<span class="sidebar-nav__toggle-bar"></span>
<span class="sidebar-nav__toggle-bar"></span>
<span class="sidebar-nav__toggle-bar"></span>
</button>
<aside class="sidebar-nav" aria-label="<?php esc_attr_e( 'Основная навигация', 'bitwms-child' ); ?>">
<div class="sidebar-nav__logo">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" aria-label="<?php esc_attr_e( 'На главную', 'bitwms-child' ); ?>">
<img
src="<?php echo esc_url( $theme_uri . '/img/logo-short.svg' ); ?>"
alt="<?php esc_attr_e( 'БИТ.WMS', 'bitwms-child' ); ?>"
width="32"
height="32"
/>
</a>
</div>
<nav class="sidebar-nav__menu" aria-label="<?php esc_attr_e( 'Разделы сайта', 'bitwms-child' ); ?>">
<ul class="sidebar-nav__list">
<?php foreach ( $nav_items as $item ) :
$is_active = rtrim( $current_path, '/' ) === rtrim( $item['url'], '/' );
$item_class = 'sidebar-nav__item' . ( $is_active ? ' sidebar-nav__item--active' : '' );
$icon_src = esc_url( $theme_uri . '/img/icons/' . $item['icon'] );
?>
<li class="<?php echo esc_attr( $item_class ); ?>">
<a
href="<?php echo esc_url( $item['url'] ); ?>"
class="sidebar-nav__link"
<?php if ( $is_active ) : ?>aria-current="page"<?php endif; ?>
>
<img
class="sidebar-nav__icon"
src="<?php echo $icon_src; ?>"
alt=""
width="24"
height="24"
aria-hidden="true"
/>
<span class="sidebar-nav__label"><?php echo esc_html( $item['label'] ); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</nav>
<div class="sidebar-nav__social">
<a
href="https://t.me/bitwms"
class="sidebar-nav__social-link"
target="_blank"
rel="noopener noreferrer"
aria-label="<?php esc_attr_e( 'Telegram', 'bitwms-child' ); ?>"
>
<img
src="<?php echo esc_url( $theme_uri . '/img/icons/telegram.svg' ); ?>"
alt="Telegram"
width="24"
height="24"
/>
</a>
<a
href="https://vk.com/bitwms"
class="sidebar-nav__social-link"
target="_blank"
rel="noopener noreferrer"
aria-label="<?php esc_attr_e( 'ВКонтакте', 'bitwms-child' ); ?>"
>
<img
src="<?php echo esc_url( $theme_uri . '/img/icons/vk.svg' ); ?>"
alt="ВКонтакте"
width="24"
height="24"
/>
</a>
</div>
</aside>