Stimulus controller (name: 'content-swipe-tab' )
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ["tabs", 'tabsPane', "swipeArea"]
//[TODO] перенести у static values
static swipeThreshold = 100 // Пороговое значение для определения свайпа (чтобы мелкие движения не учитывались)
startX = null
startY = null
showTab(event) {
event.preventDefault()
const tab = event.target.closest("[data-bs-toggle='tab']") // Находим вкладку, на которую кликнули
if (tab && !tab.classList.contains("active")) {
this.activateTab(tab) // Активируем вкладку, если она не активна
}
}
swipeLeft() {
const currentTab = this.getCurrentTab()
const nextTab = currentTab.closest('li').nextElementSibling // Получаем следующую вкладку (стандарт -> currentTab.nextElementSibling)
if (nextTab) {
this.activateTab(nextTab.querySelector('button')) // Активируем следующую вкладку (стандарт -> nextTab)
}
}
swipeRight() {
const currentTab = this.getCurrentTab()
const prevTab = currentTab.closest('li').previousElementSibling // Получаем предыдущую вкладку (стандарт -> currentTab.nextElementSibling)
if (prevTab) {
this.activateTab(prevTab.querySelector('button')) // Активируем предыдущую вкладку (стандарт -> nextTab)
}
}
connect() {
// Добавляем обработчик события touchstart
this.swipeAreaTarget.addEventListener("touchstart", (event) => {
this.startX = event.touches[0].clientX
this.startY = event.touches[0].clientY
})
// Добавляем обработчик события touchend
this.swipeAreaTarget.addEventListener("touchend", (event) => {
const endX = event.changedTouches[0].clientX
const endY = event.changedTouches[0].clientY
const diffX = this.startX - endX
const diffY = this.startY - endY
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > this.constructor.swipeThreshold) { // Если движение по X больше движения по Y и больше порогового значения
if (diffX > 0) {
this.swipeLeft() // предыдущая вкладка
} else {
this.swipeRight() // следующая вкладка
}
}
})
}
getCurrentTab() {
return this.tabsTargets.find((tab) => tab.classList.contains("active")) // Находим активную вкладку
}
activateTab(tab) {
this.tabsTargets.forEach((t) => t.classList.remove("active")) // Удаляем класс "active" со всех вкладок
tab.classList.add("active") // Добавляем класс "active" к текущей вкладке
this.tabsPaneTargets.forEach((pane) => pane.classList.remove("show", "active")) // Удаляем класс active из всех элементов, которые соответствуют вкладкам
const target = document.querySelector(tab.dataset.bsTarget) // Получаем элемент, соответствующий вкладке
if (target) target.classList.add("show", "active") / Добавляем класс 'show active' к элементу, который соответствует вкладке
}
}
sample.htm.erb (with Bootstrap 5 Tab)
<div class="container-fluid" id='main-page-container' data-controller="content-swipe-tab"> <%# Stimulus контроллер %>
<h2 class="title text-center mb-5"><%= t('pages.components.index.title') %></h2>
<ul class="nav nav-tabs justify-content-center mb-3"
id="sectionTab"
role="tablist">
<li class="nav-item px-3" role="presentation">
<button class="nav-link active"
id="network-tab"
data-bs-toggle="tab"
data-bs-target="#network-tab-pane"
type="button"
role="tab"
aria-controls="network-tab-pane"
aria-selected="true"
data-content-swipe-tab-target='tabs'> <%# Stimulus targets 'tabs' %>
<img class="svg-icon me-2" src="/icons/hero-pen.svg"/>
<%= t('pages.components.index.tabs.title.posts') %>
</button>
</li>
<li class="nav-item px-3" role="presentation">
<button class="nav-link"
id="store-tab"
data-bs-toggle="tab"
data-bs-target="#store-tab-pane"
type="button"
role="tab"
aria-controls="store-tab-pane"
aria-selected="false"
data-content-swipe-tab-target='tabs'> <%# Stimulus targets 'tabs' %>
<img class="svg-icon me-2" src="/icons/hero-cart.svg"/>
<%= t('pages.components.index.tabs.title.products') %>
</button>
</li>
<li class="nav-item px-3" role="presentation">
<button class="nav-link"
id="services-tab"
data-bs-toggle="tab"
data-bs-target="#services-tab-pane"
type="button"
role="tab"
aria-controls="services-tab-pane"
aria-selected="false"
data-content-swipe-tab-target='tabs'> <%# Stimulus targets 'tabs' %>
<img class="svg-icon me-2" src="/icons/hero-heart.svg"/>
<%= t('pages.components.index.tabs.title.services') %>
</button>
</li>
</ul>
<div class="tab-content" id="nav-tabContent" data-content-swipe-tab-target="swipeArea"> <%# Stimulus targets 'swipeArea' %>
<div class="tab-pane fade show active"
id="network-tab-pane"
role="tabpanel"
aria-labelledby="network-tab"
tabindex="0"
data-content-swipe-tab-target='tabsPane'> <%# Stimulus targets 'tabPane' %>
<div class="indexbeg">
<div class="container content d-flex justify-content-evenly">
<div>
<img class="tab-image" src="/images/hero-network.png" />
</div>
<div class="hero-text m-0">
<h3 class="tab-title"><%= t('pages.components.index.section.posts.title') %></h3>
<h4 class="tab-subtitle my-2"><%= t('pages.components.index.section.posts.subtitle') %></h4>
<div class='d-flex'>
<%= link_to posts_path, class: 'btn btn-new-outline mt-4' do %>
<%= t('pages.components.index.section.goto') %>
<img class="arrow" src="/Arrow1.svg" />
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade"
id="store-tab-pane"
role="tabpanel"
aria-labelledby="store-tab"
tabindex="1"
data-content-swipe-tab-target='tabsPane'> <%# Stimulus targets 'tabPane' %>
<div class="indexbeg">
<div class="container content d-flex justify-content-evenly">
<div>
<img class="tab-image" src="/images/hero-announ.png" />
</div>
<div class="hero-text m-0">
<h3 class="tab-title"><%= t('pages.components.index.section.products.title') %></h3>
<h4 class="tab-subtitle my-2"><%= t('pages.components.index.section.products.subtitle') %></h4>
<div class='d-flex'>
<%= link_to category_path('ihrashky'), class: 'btn btn-new-outline mt-4' do %>
<%= t('pages.components.index.section.goto') %>
<img class="arrow" src="/Arrow1.svg" />
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade"
id="services-tab-pane"
role="tabpanel"
aria-labelledby="services-tab"
tabindex="2"
data-content-swipe-tab-target='tabsPane'> <%# Stimulus targets 'tabPane' %>
<div class="indexbeg">
<div class="container content d-flex justify-content-evenly">
<div>
<img class="tab-image" src="/images/hero-services.png" />
</div>
<div class="hero-text m-0">
<h3 class="tab-title"><%= t('pages.components.index.section.services.title') %></h3>
<h4 class="tab-subtitle my-2"><%= t('pages.components.index.section.services.subtitle') %></h4>
<%= render 'pages/shared/link_alert' %>
</div>
</div>
</div>
</div>
</div>
</div>
Цей варіант передбачає що при обранні однієї з вкладок, в іншому компоненті вкладок буде одночасно активована інша, відповідно контексту, вкладка