Created
September 28, 2025 02:01
-
-
Save zilveer/8fa4c57d5c3b358952651b9aad32543b to your computer and use it in GitHub Desktop.
Nested swipeable tabs demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Nested Tabs with Swipe Panels</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| background: #f5f5f5; | |
| padding: 20px; | |
| } | |
| .tabs-container { | |
| background: white; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| overflow: hidden; | |
| margin-bottom: 20px; | |
| } | |
| .tabs-nav { | |
| display: flex; | |
| background: #f8f9fa; | |
| border-bottom: 1px solid #e9ecef; | |
| position: relative; | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| scroll-behavior: smooth; | |
| scrollbar-width: none; /* Firefox */ | |
| -ms-overflow-style: none; /* IE/Edge */ | |
| } | |
| .tabs-nav::-webkit-scrollbar { | |
| display: none; /* Chrome/Safari */ | |
| } | |
| .tab-button { | |
| flex: 0 0 auto; | |
| min-width: 120px; | |
| padding: 16px 20px; | |
| border: none; | |
| background: none; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: #6c757d; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| white-space: nowrap; | |
| } | |
| .tab-button:hover { | |
| color: #495057; | |
| background: rgba(0,0,0,0.05); | |
| } | |
| .tab-button.active { | |
| color: #007bff; | |
| } | |
| .tab-indicator { | |
| position: absolute; | |
| bottom: 0; | |
| height: 3px; | |
| background: #007bff; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| border-radius: 3px 3px 0 0; | |
| } | |
| .panels-container { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .panel { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| padding: 24px; | |
| opacity: 0; | |
| transform: translateX(30px); | |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| pointer-events: none; | |
| min-height: 200px; | |
| } | |
| .panel.active { | |
| position: relative; | |
| opacity: 1; | |
| transform: translateX(0); | |
| pointer-events: all; | |
| } | |
| .panel h3 { | |
| margin-bottom: 16px; | |
| color: #343a40; | |
| } | |
| .panel p { | |
| color: #6c757d; | |
| line-height: 1.6; | |
| margin-bottom: 12px; | |
| } | |
| /* Nested tabs styling */ | |
| .nested-tabs { | |
| margin-top: 20px; | |
| } | |
| .nested-tabs .tabs-nav { | |
| background: #f1f3f4; | |
| } | |
| .nested-tabs .tab-button { | |
| font-size: 13px; | |
| padding: 12px 16px; | |
| min-width: 100px; | |
| } | |
| .nested-tabs .tab-indicator { | |
| background: #28a745; | |
| } | |
| .nested-tabs .tab-button.active { | |
| color: #28a745; | |
| } | |
| /* Touch/swipe support */ | |
| .swipeable { | |
| touch-action: pan-y; | |
| user-select: none; | |
| } | |
| /* Demo controls */ | |
| .demo-controls { | |
| margin-bottom: 20px; | |
| display: flex; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .demo-controls button { | |
| padding: 10px 16px; | |
| border: 1px solid #ddd; | |
| background: white; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: all 0.2s; | |
| } | |
| .demo-controls button:hover { | |
| background: #f8f9fa; | |
| border-color: #007bff; | |
| } | |
| @media (max-width: 768px) { | |
| .tab-button { | |
| min-width: 100px; | |
| padding: 12px 16px; | |
| } | |
| .nested-tabs .tab-button { | |
| min-width: 80px; | |
| padding: 10px 12px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="demo-controls"> | |
| <button onclick="addDynamicTab()">Add Dynamic Tab</button> | |
| <button onclick="removeDynamicTab()">Remove Last Tab</button> | |
| <button onclick="loadFromDOM()">Load Nested Tabs</button> | |
| <button onclick="loadParentTabsFromDOM()">Load Parent Tabs from DOM</button> | |
| <button onclick="addNestedDynamicTab()">Add Nested Dynamic Tab</button> | |
| <button onclick="showExamples()">Show Code Examples</button> | |
| </div> | |
| <!-- Main tabs container --> | |
| <div id="mainTabs" class="tabs-container" data-tabs-group="main"> | |
| <div class="tabs-nav"> | |
| <button class="tab-button active" data-tab="overview">Overview</button> | |
| <button class="tab-button" data-tab="features">Features</button> | |
| <button class="tab-button" data-tab="settings">Settings</button> | |
| <button class="tab-button" data-tab="analytics">Analytics</button> | |
| <button class="tab-button" data-tab="reports">Reports</button> | |
| <button class="tab-button" data-tab="users">Users</button> | |
| <button class="tab-button" data-tab="admin">Admin</button> | |
| </div> | |
| <div class="panels-container swipeable"> | |
| <div class="panel active" data-panel="overview"> | |
| <h3>Overview</h3> | |
| <p>This is the overview panel with some placeholder content. You can swipe left/right on mobile devices to navigate between panels.</p> | |
| <p>The tabs system supports nested structures and dynamic content loading.</p> | |
| </div> | |
| <div class="panel" data-panel="features"> | |
| <h3>Features</h3> | |
| <p>Key features of this tab system:</p> | |
| <p>β’ Event delegation for efficient event handling</p> | |
| <p>β’ Swipe gesture support for mobile devices</p> | |
| <p>β’ Smooth animations and transitions</p> | |
| <p>β’ Dynamic tab addition/removal</p> | |
| <p>β’ Nested tabs support</p> | |
| <p>β’ Horizontally scrollable tabs with auto-centering</p> | |
| </div> | |
| <div class="panel" data-panel="settings"> | |
| <h3>Settings</h3> | |
| <p>Settings panel content goes here. This demonstrates how content can be organized in different tabs.</p> | |
| <p>Each panel can contain any type of content including forms, images, or even nested tab structures.</p> | |
| </div> | |
| <div class="panel" data-panel="analytics"> | |
| <h3>Analytics</h3> | |
| <p>Analytics dashboard with charts and metrics.</p> | |
| <p>This tab demonstrates horizontal scrolling when there are many tabs.</p> | |
| </div> | |
| <div class="panel" data-panel="reports"> | |
| <h3>Reports</h3> | |
| <p>Comprehensive reporting tools and data visualization.</p> | |
| <p>Notice how the active tab centers itself in the navigation bar.</p> | |
| </div> | |
| <div class="panel" data-panel="users"> | |
| <h3>Users</h3> | |
| <p>User management and administration panel.</p> | |
| <p>The tab navigation smoothly scrolls to keep the active tab visible.</p> | |
| </div> | |
| <div class="panel" data-panel="admin"> | |
| <h3>Admin</h3> | |
| <p>System administration and configuration settings.</p> | |
| <p>This is the last tab to demonstrate the scrolling behavior.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Hidden template for nested tabs (loaded via DOM) --> | |
| <div id="nestedTemplate" style="display: none;"> | |
| <div class="tabs-container nested-tabs" data-tabs-group="nested"> | |
| <div class="tabs-nav"> | |
| <button class="tab-button active" data-tab="general">General</button> | |
| <button class="tab-button" data-tab="advanced">Advanced</button> | |
| <button class="tab-button" data-tab="security">Security</button> | |
| </div> | |
| <div class="panels-container swipeable"> | |
| <div class="panel active" data-panel="general"> | |
| <h3>General Settings</h3> | |
| <p>General configuration options and preferences.</p> | |
| <p><em>Loaded from DOM template!</em></p> | |
| </div> | |
| <div class="panel" data-panel="advanced"> | |
| <h3>Advanced Settings</h3> | |
| <p>Advanced configuration for power users.</p> | |
| <p><em>This nested tab structure was loaded from a hidden DOM template.</em></p> | |
| </div> | |
| <div class="panel" data-panel="security"> | |
| <h3>Security Settings</h3> | |
| <p>Security and privacy related configurations.</p> | |
| <p><em>Each nested tab maintains its own state independently.</em></p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Hidden template for parent tabs (loaded via DOM) --> | |
| <div id="parentTabsTemplate" style="display: none;"> | |
| <div class="tabs-container" data-tabs-group="loaded-parent"> | |
| <div class="tabs-nav"> | |
| <button class="tab-button active" data-tab="dashboard">Dashboard</button> | |
| <button class="tab-button" data-tab="projects">Projects</button> | |
| <button class="tab-button" data-tab="team">Team</button> | |
| <button class="tab-button" data-tab="billing">Billing</button> | |
| </div> | |
| <div class="panels-container swipeable"> | |
| <div class="panel active" data-panel="dashboard"> | |
| <h3>Dashboard</h3> | |
| <p>Main dashboard with key metrics and overview.</p> | |
| <p><strong>This entire tab structure was loaded from DOM!</strong></p> | |
| <p>You can click through these tabs and they work perfectly.</p> | |
| </div> | |
| <div class="panel" data-panel="projects"> | |
| <h3>Projects</h3> | |
| <p>Project management and tracking interface.</p> | |
| <p>All tabs support swipe gestures and auto-centering.</p> | |
| </div> | |
| <div class="panel" data-panel="team"> | |
| <h3>Team</h3> | |
| <p>Team collaboration and member management.</p> | |
| <p>Event delegation handles all interactions automatically.</p> | |
| </div> | |
| <div class="panel" data-panel="billing"> | |
| <h3>Billing</h3> | |
| <p>Subscription and payment management.</p> | |
| <p>Perfect for loading complex tab structures at runtime!</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Container for dynamically loaded content --> | |
| <div id="dynamicContainer"></div> | |
| <script> | |
| class TabsManager { | |
| constructor() { | |
| this.activeGroups = new Map(); | |
| this.swipeThreshold = 50; | |
| this.swipeStartX = 0; | |
| this.init(); | |
| } | |
| init() { | |
| // Event delegation for all tab interactions | |
| document.addEventListener('click', this.handleTabClick.bind(this)); | |
| document.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true }); | |
| document.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: true }); | |
| // Initialize existing tab groups | |
| this.initializeTabGroups(); | |
| } | |
| initializeTabGroups() { | |
| const tabGroups = document.querySelectorAll('[data-tabs-group]'); | |
| tabGroups.forEach(group => { | |
| const groupId = group.dataset.tabsGroup; | |
| const activeTab = group.querySelector('.tab-button.active'); | |
| if (activeTab) { | |
| this.activeGroups.set(groupId, activeTab.dataset.tab); | |
| } | |
| this.updateTabIndicator(group); | |
| this.centerActiveTab(group); | |
| }); | |
| } | |
| handleTabClick(e) { | |
| const tabButton = e.target.closest('.tab-button'); | |
| if (!tabButton) return; | |
| e.preventDefault(); | |
| const container = tabButton.closest('[data-tabs-group]'); | |
| const groupId = container.dataset.tabsGroup; | |
| const tabId = tabButton.dataset.tab; | |
| this.activateTab(container, tabId); | |
| this.activeGroups.set(groupId, tabId); | |
| } | |
| activateTab(container, tabId) { | |
| // Update buttons | |
| const buttons = container.querySelectorAll('.tab-button'); | |
| buttons.forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.tab === tabId); | |
| }); | |
| // Update panels - use direct child selector to avoid nested panels | |
| const panelsContainer = container.querySelector('.panels-container'); | |
| const panels = panelsContainer.children; | |
| Array.from(panels).forEach(panel => { | |
| if (panel.classList.contains('panel')) { | |
| panel.classList.toggle('active', panel.dataset.panel === tabId); | |
| } | |
| }); | |
| this.updateTabIndicator(container); | |
| this.centerActiveTab(container); | |
| } | |
| addTab(containerId, tabId, tabLabel, panelContent) { | |
| const container = document.getElementById(containerId); | |
| if (!container) return; | |
| this.addTabToContainer(container, tabId, tabLabel, panelContent); | |
| } | |
| updateTabIndicator(container) { | |
| const activeButton = container.querySelector('.tab-button.active'); | |
| let indicator = container.querySelector('.tab-indicator'); | |
| if (!indicator) { | |
| indicator = document.createElement('div'); | |
| indicator.className = 'tab-indicator'; | |
| container.querySelector('.tabs-nav').appendChild(indicator); | |
| } | |
| if (activeButton) { | |
| const rect = activeButton.getBoundingClientRect(); | |
| const navRect = container.querySelector('.tabs-nav').getBoundingClientRect(); | |
| indicator.style.left = (activeButton.offsetLeft) + 'px'; | |
| indicator.style.width = activeButton.offsetWidth + 'px'; | |
| } | |
| } | |
| centerActiveTab(container) { | |
| const activeButton = container.querySelector('.tab-button.active'); | |
| const tabsNav = container.querySelector('.tabs-nav'); | |
| if (!activeButton || !tabsNav) return; | |
| const navWidth = tabsNav.offsetWidth; | |
| const navScrollWidth = tabsNav.scrollWidth; | |
| // Only scroll if content overflows | |
| if (navScrollWidth <= navWidth) return; | |
| const buttonLeft = activeButton.offsetLeft; | |
| const buttonWidth = activeButton.offsetWidth; | |
| const buttonCenter = buttonLeft + (buttonWidth / 2); | |
| // Calculate scroll position to center the active tab | |
| const targetScrollLeft = buttonCenter - (navWidth / 2); | |
| // Constrain scroll within bounds | |
| const maxScrollLeft = navScrollWidth - navWidth; | |
| const finalScrollLeft = Math.max(0, Math.min(targetScrollLeft, maxScrollLeft)); | |
| tabsNav.scrollTo({ | |
| left: finalScrollLeft, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| handleTouchStart(e) { | |
| const swipeableArea = e.target.closest('.swipeable'); | |
| if (!swipeableArea) return; | |
| this.swipeStartX = e.touches[0].clientX; | |
| this.currentSwipeContainer = swipeableArea.closest('[data-tabs-group]'); | |
| } | |
| handleTouchEnd(e) { | |
| if (!this.currentSwipeContainer) return; | |
| const swipeEndX = e.changedTouches[0].clientX; | |
| const swipeDistance = this.swipeStartX - swipeEndX; | |
| if (Math.abs(swipeDistance) > this.swipeThreshold) { | |
| // Only get direct child tab buttons, not nested ones | |
| const tabsNav = this.currentSwipeContainer.querySelector(':scope > .tabs-nav'); | |
| const buttons = tabsNav.querySelectorAll('.tab-button'); | |
| const activeIndex = Array.from(buttons).findIndex(btn => btn.classList.contains('active')); | |
| let newIndex; | |
| if (swipeDistance > 0 && activeIndex < buttons.length - 1) { | |
| // Swipe left - next tab | |
| newIndex = activeIndex + 1; | |
| } else if (swipeDistance < 0 && activeIndex > 0) { | |
| // Swipe right - previous tab | |
| newIndex = activeIndex - 1; | |
| } | |
| if (newIndex !== undefined) { | |
| const newTab = buttons[newIndex].dataset.tab; | |
| this.activateTab(this.currentSwipeContainer, newTab); | |
| this.activeGroups.set(this.currentSwipeContainer.dataset.tabsGroup, newTab); | |
| } | |
| } | |
| this.currentSwipeContainer = null; | |
| } | |
| addTabToContainer(container, tabId, tabLabel, panelContent) { | |
| // Add tab button | |
| const tabsNav = container.querySelector('.tabs-nav'); | |
| const newButton = document.createElement('button'); | |
| newButton.className = 'tab-button'; | |
| newButton.dataset.tab = tabId; | |
| newButton.textContent = tabLabel; | |
| // Insert before indicator if it exists | |
| const indicator = tabsNav.querySelector('.tab-indicator'); | |
| if (indicator) { | |
| tabsNav.insertBefore(newButton, indicator); | |
| } else { | |
| tabsNav.appendChild(newButton); | |
| } | |
| // Add panel | |
| const panelsContainer = container.querySelector('.panels-container'); | |
| const newPanel = document.createElement('div'); | |
| newPanel.className = 'panel'; | |
| newPanel.dataset.panel = tabId; | |
| newPanel.innerHTML = panelContent; | |
| panelsContainer.appendChild(newPanel); | |
| this.updateTabIndicator(container); | |
| this.centerActiveTab(container); | |
| } | |
| removeTab(containerId, tabId) { | |
| const container = document.getElementById(containerId); | |
| if (!container) return; | |
| const button = container.querySelector(`[data-tab="${tabId}"]`); | |
| const panel = container.querySelector(`[data-panel="${tabId}"]`); | |
| if (button) button.remove(); | |
| if (panel) panel.remove(); | |
| // If removed tab was active, activate first available tab | |
| if (button && button.classList.contains('active')) { | |
| const firstButton = container.querySelector('.tab-button'); | |
| if (firstButton) { | |
| this.activateTab(container, firstButton.dataset.tab); | |
| } | |
| } | |
| this.updateTabIndicator(container); | |
| } | |
| loadFromDOM(sourceId, targetId) { | |
| const source = document.getElementById(sourceId); | |
| const target = document.getElementById(targetId); | |
| if (!source || !target) return; | |
| const clonedContent = source.cloneNode(true); | |
| clonedContent.style.display = 'block'; | |
| clonedContent.id = ''; | |
| target.appendChild(clonedContent); | |
| this.initializeTabGroups(); | |
| } | |
| } | |
| // Initialize the tabs manager | |
| const tabsManager = new TabsManager(); | |
| // Demo functions | |
| let dynamicTabCount = 0; | |
| let nestedDynamicTabCount = 0; | |
| // 1. ADD DYNAMIC PARENT TAB | |
| function addDynamicTab() { | |
| dynamicTabCount++; | |
| const tabId = `dynamic-${dynamicTabCount}`; | |
| const content = ` | |
| <h3>Dynamic Tab ${dynamicTabCount}</h3> | |
| <p>This tab was added dynamically at runtime.</p> | |
| <p>Tab ID: ${tabId}</p> | |
| <p>Created at: ${new Date().toLocaleTimeString()}</p> | |
| <p><strong>Added via JavaScript:</strong></p> | |
| <pre style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px;"> | |
| tabsManager.addTab('mainTabs', '${tabId}', 'Dynamic ${dynamicTabCount}', content); | |
| </pre> | |
| `; | |
| tabsManager.addTab('mainTabs', tabId, `Dynamic ${dynamicTabCount}`, content); | |
| } | |
| // 2. REMOVE DYNAMIC TAB | |
| function removeDynamicTab() { | |
| if (dynamicTabCount > 0) { | |
| const tabId = `dynamic-${dynamicTabCount}`; | |
| tabsManager.removeTab('mainTabs', tabId); | |
| dynamicTabCount--; | |
| } | |
| } | |
| // 3. LOAD NESTED TABS FROM DOM | |
| function loadFromDOM() { | |
| const settingsPanel = document.querySelector('[data-panel="settings"]'); | |
| // Check if nested tabs already exist | |
| if (settingsPanel.querySelector('.nested-tabs')) { | |
| alert('Nested tabs already loaded!'); | |
| return; | |
| } | |
| // Clone the template and append to settings panel | |
| const template = document.getElementById('nestedTemplate'); | |
| const clonedContent = template.cloneNode(true); | |
| clonedContent.style.display = 'block'; | |
| clonedContent.id = ''; | |
| settingsPanel.appendChild(clonedContent); | |
| tabsManager.initializeTabGroups(); | |
| } | |
| // 4. LOAD PARENT TABS FROM DOM | |
| function loadParentTabsFromDOM() { | |
| const container = document.getElementById('dynamicContainer'); | |
| // Clear existing content | |
| container.innerHTML = ''; | |
| // Clone the parent tabs template | |
| const template = document.getElementById('parentTabsTemplate'); | |
| const clonedContent = template.cloneNode(true); | |
| clonedContent.style.display = 'block'; | |
| clonedContent.id = ''; | |
| container.appendChild(clonedContent); | |
| tabsManager.initializeTabGroups(); | |
| } | |
| // 5. ADD DYNAMIC NESTED TAB | |
| function addNestedDynamicTab() { | |
| const nestedContainer = document.querySelector('[data-tabs-group="nested"]'); | |
| if (!nestedContainer) { | |
| alert('Please load nested tabs first!'); | |
| return; | |
| } | |
| nestedDynamicTabCount++; | |
| const tabId = `nested-dynamic-${nestedDynamicTabCount}`; | |
| const content = ` | |
| <h3>Nested Dynamic ${nestedDynamicTabCount}</h3> | |
| <p>This nested tab was added dynamically!</p> | |
| <p>Nested tabs work independently from parent tabs.</p> | |
| `; | |
| // Add to the nested container | |
| tabsManager.addTab(nestedContainer.closest('[data-tabs-group]').parentNode.id || 'nested-container', tabId, `Nested ${nestedDynamicTabCount}`, content); | |
| // Since we don't have direct container ID, we'll use the class-based approach | |
| tabsManager.addTabToContainer(nestedContainer, tabId, `Nested ${nestedDynamicTabCount}`, content); | |
| } | |
| // 6. SHOW CODE EXAMPLES | |
| function showExamples() { | |
| const exampleContent = ` | |
| <h3>π Code Examples</h3> | |
| <h4>π― 1. Add Dynamic Tab</h4> | |
| <pre style="background: #f8f9fa; padding: 15px; border-radius: 6px; overflow-x: auto;"> | |
| // Add a new tab dynamically | |
| tabsManager.addTab( | |
| 'containerId', // Container ID | |
| 'newTabId', // Unique tab ID | |
| 'Tab Label', // Display name | |
| '<h3>Content</h3>' // HTML content | |
| ); | |
| </pre> | |
| <h4>ποΈ 2. Remove Tab</h4> | |
| <pre style="background: #f8f9fa; padding: 15px; border-radius: 6px; overflow-x: auto;"> | |
| // Remove a tab | |
| tabsManager.removeTab('containerId', 'tabId'); | |
| </pre> | |
| <h4>π 3. Load from DOM Template</h4> | |
| <pre style="background: #f8f9fa; padding: 15px; border-radius: 6px; overflow-x: auto;"> | |
| // HTML Template (hidden) | |
| <div id="myTemplate" style="display: none;"> | |
| <div class="tabs-container" data-tabs-group="myGroup"> | |
| <div class="tabs-nav"> | |
| <button class="tab-button active" data-tab="tab1">Tab 1</button> | |
| </div> | |
| <div class="panels-container swipeable"> | |
| <div class="panel active" data-panel="tab1">Content</div> | |
| </div> | |
| </div> | |
| </div> | |
| // JavaScript to load | |
| const template = document.getElementById('myTemplate'); | |
| const clone = template.cloneNode(true); | |
| clone.style.display = 'block'; | |
| clone.id = ''; | |
| targetContainer.appendChild(clone); | |
| tabsManager.initializeTabGroups(); | |
| </pre> | |
| <h4>π§ 4. Direct Container Method</h4> | |
| <pre style="background: #f8f9fa; padding: 15px; border-radius: 6px; overflow-x: auto;"> | |
| // Add tab to specific container element | |
| const container = document.querySelector('[data-tabs-group="myGroup"]'); | |
| tabsManager.addTabToContainer(container, 'tabId', 'Label', 'Content'); | |
| </pre> | |
| <h4>π¨ 5. Create Complete Structure Dynamically</h4> | |
| <pre style="background: #f8f9fa; padding: 15px; border-radius: 6px; overflow-x: auto;"> | |
| // Create a complete tab structure | |
| const tabStructure = \` | |
| <div class="tabs-container" data-tabs-group="dynamic"> | |
| <div class="tabs-nav"> | |
| <button class="tab-button active" data-tab="home">Home</button> | |
| <button class="tab-button" data-tab="about">About</button> | |
| </div> | |
| <div class="panels-container swipeable"> | |
| <div class="panel active" data-panel="home">Home Content</div> | |
| <div class="panel" data-panel="about">About Content</div> | |
| </div> | |
| </div>\`; | |
| document.body.insertAdjacentHTML('beforeend', tabStructure); | |
| tabsManager.initializeTabGroups(); | |
| </pre> | |
| <h4>π± Key Features Available:</h4> | |
| <ul style="margin: 15px 0;"> | |
| <li>β Event delegation (no need to rebind events)</li> | |
| <li>β Auto-centering active tabs</li> | |
| <li>β Swipe gesture support</li> | |
| <li>β Smooth animations</li> | |
| <li>β Nested tab support</li> | |
| <li>β Dynamic add/remove</li> | |
| <li>β Horizontal scrolling</li> | |
| </ul> | |
| `; | |
| // Create or update examples tab | |
| const existingPanel = document.querySelector('[data-panel="examples"]'); | |
| if (existingPanel) { | |
| existingPanel.innerHTML = exampleContent; | |
| tabsManager.activateTab(document.getElementById('mainTabs'), 'examples'); | |
| } else { | |
| tabsManager.addTab('mainTabs', 'examples', 'π Examples', exampleContent); | |
| tabsManager.activateTab(document.getElementById('mainTabs'), 'examples'); | |
| } | |
| } | |
| // Handle window resize for tab indicators and centering | |
| window.addEventListener('resize', () => { | |
| const tabGroups = document.querySelectorAll('[data-tabs-group]'); | |
| tabGroups.forEach(group => { | |
| tabsManager.updateTabIndicator(group); | |
| tabsManager.centerActiveTab(group); | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment