Last active
July 1, 2025 18:40
-
-
Save zilveer/0c639bcc4e3d8528c1a868d2166b658f to your computer and use it in GitHub Desktop.
A58
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
A58 | |
--- | |
A [Pen](https://codepen.io/zilveer/pen/xbGNKeQ) by [zilveer](https://codepen.io/zilveer) on [CodePen](https://codepen.io). | |
[License](https://codepen.io/license/pen/xbGNKeQ). |
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"> | |
<title>Minimal SPA Router</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: #333; | |
line-height: 1.6; | |
min-height: 100vh; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
header { | |
margin-bottom: 30px; | |
text-align: center; | |
} | |
h1 { | |
font-size: 2.5rem; | |
font-weight: 300; | |
color: white; | |
text-shadow: 0 2px 10px rgba(0,0,0,0.3); | |
margin-bottom: 10px; | |
} | |
.subtitle { | |
color: rgba(255,255,255,0.8); | |
font-size: 1.1rem; | |
font-weight: 300; | |
} | |
.main-nav { | |
background: rgba(255,255,255,0.95); | |
backdrop-filter: blur(10px); | |
border-radius: 12px; | |
padding: 0; | |
margin-bottom: 20px; | |
box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
border: 1px solid rgba(255,255,255,0.2); | |
} | |
.nav-list { | |
list-style: none; | |
display: flex; | |
margin: 0; | |
padding: 0; | |
overflow-x: auto; | |
} | |
.nav-link { | |
display: block; | |
padding: 18px 24px; | |
text-decoration: none; | |
color: #666; | |
border-bottom: 3px solid transparent; | |
transition: all 0.3s ease; | |
font-weight: 500; | |
white-space: nowrap; | |
position: relative; | |
} | |
.nav-link:hover { | |
color: #2c3e50; | |
background: rgba(52, 152, 219, 0.1); | |
transform: translateY(-2px); | |
} | |
.nav-link.active { | |
color: #3498db; | |
border-bottom-color: #3498db; | |
background: rgba(52, 152, 219, 0.1); | |
} | |
.nav-link.active::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
height: 2px; | |
background: linear-gradient(90deg, #3498db, #2980b9); | |
} | |
.content-area, .tab-content { | |
background: rgba(255,255,255,0.95); | |
backdrop-filter: blur(10px); | |
border-radius: 12px; | |
padding: 30px; | |
margin-bottom: 20px; | |
box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
border: 1px solid rgba(255,255,255,0.2); | |
min-height: 200px; | |
transition: all 0.3s ease; | |
} | |
.content-area:hover, .tab-content:hover { | |
transform: translateY(-4px); | |
box-shadow: 0 12px 40px rgba(0,0,0,0.15); | |
} | |
.sub-nav { | |
margin: 20px 0; | |
padding: 0; | |
list-style: none; | |
display: flex; | |
gap: 12px; | |
flex-wrap: wrap; | |
} | |
.sub-nav .nav-link { | |
background: linear-gradient(135deg, #f8f9fa, #e9ecef); | |
border: 1px solid rgba(255,255,255,0.3); | |
border-radius: 25px; | |
padding: 10px 18px; | |
font-size: 0.9rem; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
transition: all 0.3s ease; | |
} | |
.sub-nav .nav-link:hover { | |
background: linear-gradient(135deg, #e9ecef, #dee2e6); | |
transform: translateY(-2px); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
} | |
.sub-nav .nav-link.active { | |
background: linear-gradient(135deg, #3498db, #2980b9); | |
color: white; | |
border-color: #2980b9; | |
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3); | |
} | |
.actions { | |
display: flex; | |
gap: 12px; | |
flex-wrap: wrap; | |
margin-bottom: 20px; | |
} | |
.btn { | |
padding: 12px 24px; | |
border: 1px solid rgba(255,255,255,0.3); | |
background: rgba(255,255,255,0.9); | |
color: #666; | |
text-decoration: none; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
font-size: 0.9rem; | |
font-weight: 500; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
backdrop-filter: blur(10px); | |
} | |
.btn:hover { | |
background: rgba(255,255,255,1); | |
border-color: #3498db; | |
transform: translateY(-2px); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
} | |
.btn-primary { | |
background: linear-gradient(135deg, #3498db, #2980b9); | |
color: white; | |
border-color: #2980b9; | |
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3); | |
} | |
.btn-primary:hover { | |
background: linear-gradient(135deg, #2980b9, #1f5f7a); | |
transform: translateY(-2px); | |
box-shadow: 0 6px 16px rgba(52, 152, 219, 0.4); | |
} | |
.btn-success { | |
background: linear-gradient(135deg, #27ae60, #229954); | |
color: white; | |
border-color: #229954; | |
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3); | |
} | |
.btn-warning { | |
background: linear-gradient(135deg, #f39c12, #e67e22); | |
color: white; | |
border-color: #e67e22; | |
box-shadow: 0 4px 12px rgba(243, 156, 18, 0.3); | |
} | |
.btn-danger { | |
background: linear-gradient(135deg, #e74c3c, #c0392b); | |
color: white; | |
border-color: #c0392b; | |
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3); | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,0.6); | |
backdrop-filter: blur(5px); | |
z-index: 1000; | |
animation: fadeIn 0.3s ease; | |
} | |
.modal.show { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
.modal-content { | |
background: rgba(255,255,255,0.95); | |
backdrop-filter: blur(20px); | |
border-radius: 16px; | |
padding: 35px; | |
max-width: 500px; | |
width: 90%; | |
position: relative; | |
box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
border: 1px solid rgba(255,255,255,0.3); | |
animation: slideIn 0.3s ease; | |
} | |
.modal-close { | |
position: absolute; | |
top: 20px; | |
right: 25px; | |
background: none; | |
border: none; | |
font-size: 1.8rem; | |
cursor: pointer; | |
color: #999; | |
transition: all 0.2s ease; | |
width: 32px; | |
height: 32px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
border-radius: 50%; | |
} | |
.modal-close:hover { | |
color: #e74c3c; | |
background: rgba(231, 76, 60, 0.1); | |
transform: rotate(90deg); | |
} | |
.offcanvas { | |
position: fixed; | |
top: 0; | |
left: -350px; | |
width: 350px; | |
height: 100%; | |
background: rgba(255,255,255,0.95); | |
backdrop-filter: blur(20px); | |
box-shadow: 4px 0 20px rgba(0,0,0,0.15); | |
transition: left 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
z-index: 1001; | |
padding: 25px; | |
border-right: 1px solid rgba(255,255,255,0.3); | |
overflow-y: auto; | |
} | |
.offcanvas.show { | |
left: 0; | |
} | |
.offcanvas-backdrop { | |
display: none; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,0.6); | |
backdrop-filter: blur(5px); | |
z-index: 1000; | |
animation: fadeIn 0.3s ease; | |
} | |
.offcanvas-backdrop.show { | |
display: block; | |
} | |
.offcanvas-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 25px; | |
padding-bottom: 15px; | |
border-bottom: 2px solid rgba(52, 152, 219, 0.2); | |
} | |
.offcanvas-title { | |
font-size: 1.4rem; | |
font-weight: 600; | |
color: #2c3e50; | |
margin: 0; | |
} | |
.form-group { | |
margin-bottom: 20px; | |
} | |
.form-label { | |
display: block; | |
margin-bottom: 8px; | |
font-weight: 600; | |
color: #2c3e50; | |
font-size: 0.9rem; | |
} | |
.form-control { | |
width: 100%; | |
padding: 12px 16px; | |
border: 2px solid rgba(255,255,255,0.3); | |
border-radius: 8px; | |
font-size: 1rem; | |
background: rgba(255,255,255,0.8); | |
backdrop-filter: blur(10px); | |
transition: all 0.3s ease; | |
} | |
.form-control:focus { | |
outline: none; | |
border-color: #3498db; | |
background: rgba(255,255,255,0.95); | |
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); | |
} | |
.form-control:hover { | |
border-color: rgba(52, 152, 219, 0.5); | |
} | |
select.form-control { | |
cursor: pointer; | |
} | |
textarea.form-control { | |
resize: vertical; | |
min-height: 80px; | |
} | |
.status-indicator { | |
position: fixed; | |
top: 25px; | |
right: 25px; | |
background: rgba(255,255,255,0.95); | |
backdrop-filter: blur(20px); | |
padding: 12px 18px; | |
border-radius: 25px; | |
box-shadow: 0 4px 20px rgba(0,0,0,0.15); | |
font-size: 0.85rem; | |
color: #666; | |
z-index: 999; | |
border: 1px solid rgba(255,255,255,0.3); | |
font-weight: 500; | |
} | |
.badge { | |
background: linear-gradient(135deg, #e9ecef, #dee2e6); | |
color: #495057; | |
padding: 6px 12px; | |
border-radius: 15px; | |
font-size: 0.8rem; | |
margin-right: 8px; | |
font-weight: 500; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.badge-primary { | |
background: linear-gradient(135deg, #3498db, #2980b9); | |
color: white; | |
} | |
.badge-success { | |
background: linear-gradient(135deg, #27ae60, #229954); | |
color: white; | |
} | |
.badge-warning { | |
background: linear-gradient(135deg, #f39c12, #e67e22); | |
color: white; | |
} | |
.card { | |
background: rgba(255,255,255,0.9); | |
backdrop-filter: blur(10px); | |
border-radius: 12px; | |
padding: 20px; | |
margin-bottom: 15px; | |
border: 1px solid rgba(255,255,255,0.3); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
transition: all 0.3s ease; | |
} | |
.card:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 6px 16px rgba(0,0,0,0.15); | |
} | |
.card-header { | |
font-weight: 600; | |
color: #2c3e50; | |
margin-bottom: 10px; | |
font-size: 1.1rem; | |
} | |
.card-body { | |
color: #666; | |
line-height: 1.6; | |
} | |
.notification-item { | |
padding: 15px; | |
border-bottom: 1px solid rgba(255,255,255,0.3); | |
transition: all 0.2s ease; | |
} | |
.notification-item:hover { | |
background: rgba(52, 152, 219, 0.05); | |
} | |
.notification-item:last-child { | |
border-bottom: none; | |
} | |
.notification-title { | |
font-weight: 600; | |
color: #2c3e50; | |
margin-bottom: 5px; | |
} | |
.notification-time { | |
font-size: 0.8rem; | |
color: #999; | |
} | |
.nav-section { | |
margin-bottom: 25px; | |
} | |
.nav-section-title { | |
font-size: 0.9rem; | |
font-weight: 600; | |
color: #666; | |
margin-bottom: 10px; | |
text-transform: uppercase; | |
letter-spacing: 0.5px; | |
} | |
.nav-section .nav-link { | |
display: block; | |
padding: 12px 16px; | |
color: #666; | |
text-decoration: none; | |
border-radius: 8px; | |
margin-bottom: 5px; | |
transition: all 0.3s ease; | |
border-left: 3px solid transparent; | |
} | |
.nav-section .nav-link:hover { | |
background: rgba(52, 152, 219, 0.1); | |
color: #3498db; | |
border-left-color: #3498db; | |
transform: translateX(5px); | |
} | |
.divider { | |
height: 1px; | |
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); | |
margin: 20px 0; | |
} | |
.quick-stats { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 15px; | |
margin-bottom: 25px; | |
} | |
.stat-card { | |
background: rgba(255,255,255,0.9); | |
backdrop-filter: blur(10px); | |
padding: 20px; | |
border-radius: 12px; | |
text-align: center; | |
border: 1px solid rgba(255,255,255,0.3); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
transition: all 0.3s ease; | |
} | |
.stat-card:hover { | |
transform: translateY(-4px); | |
box-shadow: 0 8px 20px rgba(0,0,0,0.15); | |
} | |
.stat-number { | |
font-size: 2rem; | |
font-weight: 700; | |
color: #3498db; | |
margin-bottom: 5px; | |
} | |
.stat-label { | |
font-size: 0.9rem; | |
color: #666; | |
font-weight: 500; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
@keyframes slideIn { | |
from { | |
opacity: 0; | |
transform: scale(0.9) translateY(-20px); | |
} | |
to { | |
opacity: 1; | |
transform: scale(1) translateY(0); | |
} | |
} | |
@media (max-width: 768px) { | |
.container { | |
padding: 15px; | |
} | |
h1 { | |
font-size: 2rem; | |
} | |
.nav-list { | |
flex-direction: column; | |
} | |
.nav-link { | |
text-align: center; | |
} | |
.actions { | |
flex-direction: column; | |
} | |
.btn { | |
text-align: center; | |
} | |
.offcanvas { | |
width: 280px; | |
left: -280px; | |
} | |
.modal-content { | |
margin: 20px; | |
width: calc(100% - 40px); | |
} | |
.quick-stats { | |
grid-template-columns: 1fr; | |
} | |
.sub-nav { | |
justify-content: center; | |
} | |
} | |
@media (max-width: 480px) { | |
.content-area, .tab-content { | |
padding: 20px; | |
} | |
.modal-content { | |
padding: 25px; | |
} | |
.offcanvas { | |
width: 100%; | |
left: -100%; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="status-indicator" id="statusIndicator"> | |
Ready | |
</div> | |
<div class="container"> | |
<header> | |
<h1>Minimal SPA Router</h1> | |
<p class="subtitle">Advanced single-page application with dynamic routing</p> | |
</header> | |
<!-- Main Navigation --> | |
<nav class="main-nav"> | |
<ul class="nav-list" id="mainNav"> | |
<li><a href="#tab/home" class="nav-link" data-tab="home">🏠 Home</a></li> | |
<li><a href="#tab/profile" class="nav-link" data-tab="profile">👤 Profile</a></li> | |
<li><a href="#tab/settings" class="nav-link" data-tab="settings">⚙️ Settings</a></li> | |
<li><a href="#tab/analytics" class="nav-link" data-tab="analytics">📊 Analytics</a></li> | |
<li><a href="#tab/projects" class="nav-link" data-tab="projects">📁 Projects</a></li> | |
</ul> | |
</nav> | |
<!-- Quick Stats --> | |
<div class="quick-stats"> | |
<div class="stat-card"> | |
<div class="stat-number">1,247</div> | |
<div class="stat-label">Active Users</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-number">89</div> | |
<div class="stat-label">Projects</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-number">156</div> | |
<div class="stat-label">Tasks Completed</div> | |
</div> | |
<div class="stat-card"> | |
<div class="stat-number">98.5%</div> | |
<div class="stat-label">Uptime</div> | |
</div> | |
</div> | |
<!-- Page Content --> | |
<div id="pageContent" class="content-area"> | |
<h2>Welcome to Your Dashboard</h2> | |
<p>This is a modern single-page application with advanced routing capabilities. Navigate through different sections using the tabs above or the action buttons below.</p> | |
<div class="divider"></div> | |
<h3>Recent Activity</h3> | |
<div class="card"> | |
<div class="card-header">Project Alpha Update</div> | |
<div class="card-body">New features have been deployed to the production environment. All systems are running smoothly.</div> | |
</div> | |
<div class="card"> | |
<div class="card-header">Team Meeting Scheduled</div> | |
<div class="card-body">Weekly team sync meeting scheduled for tomorrow at 2:00 PM. Please review the agenda beforehand.</div> | |
</div> | |
</div> | |
<!-- Tab Content --> | |
<div id="tabContent" class="tab-content"> | |
<h3>Tab Content Area</h3> | |
<p>Select a tab from the navigation above to see dynamic content loaded here. Each tab provides different functionality and options.</p> | |
<div class="actions"> | |
<button class="btn btn-primary">Primary Action</button> | |
<button class="btn btn-success">Success Action</button> | |
<button class="btn btn-warning">Warning Action</button> | |
<button class="btn btn-danger">Danger Action</button> | |
</div> | |
</div> | |
<!-- Main Actions --> | |
<div class="actions"> | |
<button class="btn btn-primary" onclick="Router.navigate({ page: { name: 'dashboard', params: { view: 'analytics' } } })"> | |
📊 Dashboard | |
</button> | |
<button class="btn btn-success" onclick="Router.navigate({ page: { name: 'projects', params: { status: 'active' } } })"> | |
📁 Projects | |
</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'menu' } })"> | |
🍔 Menu | |
</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'notifications' } })"> | |
🔔 Notifications | |
</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'login' } })"> | |
🔐 Login | |
</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'preferences' } })"> | |
⚙️ Preferences | |
</button> | |
<button class="btn btn-warning" onclick="Router.clearAll()"> | |
🗑️ Clear All | |
</button> | |
</div> | |
</div> | |
<!-- Offcanvas Menu --> | |
<div class="offcanvas-backdrop" id="offcanvasBackdrop" onclick="Router.navigate({ offcanvas: null })"></div> | |
<div class="offcanvas" id="offcanvas"> | |
<div class="offcanvas-header"> | |
<h4 class="offcanvas-title">Navigation</h4> | |
<button class="modal-close" onclick="Router.navigate({ offcanvas: null })">×</button> | |
</div> | |
<div id="offcanvasContent"> | |
<div class="nav-section"> | |
<div class="nav-section-title">Main Navigation</div> | |
<a href="#page/home" class="nav-link">🏠 Home</a> | |
<a href="#page/dashboard" class="nav-link">📊 Dashboard</a> | |
<a href="#page/projects" class="nav-link">📁 Projects</a> | |
<a href="#tab/profile" class="nav-link">👤 Profile</a> | |
<a href="#tab/settings" class="nav-link">⚙️ Settings</a> | |
</div> | |
<div class="divider"></div> | |
<div class="nav-section"> | |
<div class="nav-section-title">Quick Actions</div> | |
<a href="#" onclick="Router.navigate({ modal: { name: 'newProject' } })" class="nav-link">➕ New Project</a> | |
<a href="#" onclick="Router.navigate({ modal: { name: 'addUser' } })" class="nav-link">👥 Add User</a> | |
<a href="#" onclick="Router.navigate({ modal: { name: 'generateReport' } })" class="nav-link">📈 Generate Report</a> | |
</div> | |
<div class="divider"></div> | |
<div class="actions"> | |
<button class="btn btn-primary" onclick="Router.navigate({ modal: { name: 'quickSearch' } })">🔍 Quick Search</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'notifications' } })">🔔 Notifications</button> | |
</div> | |
</div> | |
</div> | |
<!-- Modal --> | |
<div class="modal" id="modal" onclick="closeModalOnBackdrop(event)"> | |
<div class="modal-content"> | |
<button class="modal-close" onclick="Router.navigate({ modal: null })">×</button> | |
<div id="modalContent"> | |
<h3>Modal Title</h3> | |
<p>This is a sample modal with glassmorphism effects and modern styling.</p> | |
<div class="form-group"> | |
<label class="form-label">Sample Input:</label> | |
<input type="text" class="form-control" placeholder="Enter some text..."> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Sample Select:</label> | |
<select class="form-control"> | |
<option>Option 1</option> | |
<option>Option 2</option> | |
<option>Option 3</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Sample Textarea:</label> | |
<textarea class="form-control" rows="3" placeholder="Enter your message..."></textarea> | |
</div> | |
<div class="actions"> | |
<button class="btn btn-primary">Save Changes</button> | |
<button class="btn" onclick="Router.navigate({ modal: null })">Cancel</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'relatedActions' } })">Related Actions</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Additional Modal Examples (Hidden by default) --> | |
<div class="modal" id="loginModal"> | |
<div class="modal-content"> | |
<button class="modal-close">×</button> | |
<h3>🔐 Login</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Email Address:</label> | |
<input type="email" class="form-control" placeholder="Enter your email"> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Password:</label> | |
<input type="password" class="form-control" placeholder="Enter your password"> | |
</div> | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Sign In</button> | |
<button type="button" class="btn">Forgot Password?</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
<div class="modal" id="preferencesModal"> | |
<div class="modal-content"> | |
<button class="modal-close">×</button> | |
<h3>⚙️ Preferences</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Theme:</label> | |
<select class="form-control"> | |
<option>🌞 Light</option> | |
<option>🌙 Dark</option> | |
<option>🔄 Auto</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Language:</label> | |
<select class="form-control"> | |
<option>🇺🇸 English</option> | |
<option>🇪🇸 Spanish</option> | |
<option>🇫🇷 French</option> | |
<option>🇩🇪 German</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Notifications:</label> | |
<select class="form-control"> | |
<option>🔔 All Notifications</option> | |
<option>📧 Email Only</option> | |
<option>🔕 Disabled</option> | |
</select> | |
</div> | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Save Preferences</button> | |
<button type="button" class="btn">Reset to Default</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
<!-- Additional Offcanvas Examples (Hidden by default) --> | |
<div class="offcanvas" id="notificationsOffcanvas"> | |
<div class="offcanvas-header"> | |
<h4 class="offcanvas-title">🔔 Notifications</h4> | |
<button class="modal-close">×</button> | |
</div> | |
<div> | |
<div class="notification-item"> | |
<div class="notification-title">New Project Created</div> | |
<div class="notification-time">2 minutes ago</div> | |
</div> | |
<div class="notification-item"> | |
<div class="notification-title">Report Generated Successfully</div> | |
<div class="notification-time">1 hour ago</div> | |
</div> | |
<div class="notification-item"> | |
<div class="notification-title">Team Member Added</div> | |
<div class="notification-time">3 hours ago</div> | |
</div> | |
<div class="notification-item"> | |
<div class="notification-title">System Update Available</div> | |
<div class="notification-time">5 hours ago</div> | |
</div> | |
<div class="notification-item"> | |
<div class="notification-title">Backup Completed</div> | |
<div class="notification-time">1 day ago</div> | |
</div> | |
<div class="notification-item"> | |
<div class="notification-title">New User Registration</div> | |
<div class="notification-time">2 days ago</div> | |
</div> | |
<div class="actions" style="margin-top: 20px;"> | |
<button class="btn btn-primary">Mark All Read</button> | |
<button class="btn">Clear Notifications</button> | |
</div> | |
</div> | |
</body> | |
</html> |
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
// Page Handler | |
class PageHandler { | |
static load(page) { | |
const content = page?.name | |
? this.createContent(page) | |
: '<h2>Welcome</h2><p>Select a tab from the navigation above to get started.</p>'; | |
document.getElementById('pageContent').innerHTML = content; | |
} | |
static createContent(page) { | |
const params = page.params ? Array.from(page.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div class="mt-3">${params.map(([k, v]) => `<span class="badge">${k}: ${v}</span>`).join('')}</div>` | |
: ''; | |
switch (page.name) { | |
case 'dashboard': | |
return ` | |
<h2>Dashboard</h2> | |
<p>Analytics and reporting dashboard</p> | |
${paramsList} | |
<ul class="sub-nav"> | |
<li><a href="#page/dashboard/subtab/analytics" class="nav-link" data-subtab="analytics">Analytics</a></li> | |
<li><a href="#page/dashboard/subtab/reports" class="nav-link" data-subtab="reports">Reports</a></li> | |
<li><a href="#page/dashboard/subtab/settings" class="nav-link" data-subtab="settings">Settings</a></li> | |
<li><a href="#page/dashboard/subtab/users" class="nav-link" data-subtab="users">Users</a></li> | |
</ul> | |
<div id="dashboardSubContent"> | |
<p>Select a dashboard section above.</p> | |
</div> | |
`; | |
case 'projects': | |
return ` | |
<h2>Projects</h2> | |
<p>Manage your projects and tasks</p> | |
${paramsList} | |
<ul class="sub-nav"> | |
<li><a href="#page/projects/subtab/active" class="nav-link" data-subtab="active">Active</a></li> | |
<li><a href="#page/projects/subtab/completed" class="nav-link" data-subtab="completed">Completed</a></li> | |
<li><a href="#page/projects/subtab/archived" class="nav-link" data-subtab="archived">Archived</a></li> | |
</ul> | |
<div id="projectsSubContent"> | |
<p>Select a project category above.</p> | |
</div> | |
`; | |
default: | |
return ` | |
<h2>${page.name.charAt(0).toUpperCase() + page.name.slice(1)}</h2> | |
<p>Page content for ${page.name}</p> | |
${paramsList} | |
`; | |
} | |
} | |
} | |
// Tab Handler | |
class TabHandler { | |
static load(tab) { | |
// Update active state | |
document.querySelectorAll('#mainNav .nav-link').forEach(link => { | |
link.classList.remove('active'); | |
}); | |
const content = tab?.name | |
? this.createContent(tab) | |
: '<p>Select a tab from the navigation above.</p>'; | |
document.getElementById('tabContent').innerHTML = content; | |
if (tab?.name) { | |
const activeLink = document.querySelector(`[data-tab="${tab.name}"]`); | |
if (activeLink) activeLink.classList.add('active'); | |
} | |
} | |
static createContent(tab) { | |
const params = tab.params ? Array.from(tab.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div>${params.map(([k, v]) => `<span class="badge">${k}: ${v}</span>`).join('')}</div>` | |
: ''; | |
switch (tab.name) { | |
case 'home': | |
return ` | |
<h3>Home Tab</h3> | |
<p>Welcome to your home dashboard.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'welcome' } })">Welcome Modal</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'quickActions' } })">Quick Actions</button> | |
</div> | |
`; | |
case 'profile': | |
return ` | |
<h3>Profile Tab</h3> | |
<p>Manage your profile settings and information.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'editProfile' } })">Edit Profile</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'changePassword' } })">Change Password</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'profileSettings' } })">Profile Settings</button> | |
</div> | |
`; | |
case 'settings': | |
return ` | |
<h3>Settings Tab</h3> | |
<p>Configure application settings and preferences.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'preferences' } })">Preferences</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'notifications' } })">Notifications</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'advancedSettings' } })">Advanced</button> | |
</div> | |
`; | |
default: | |
return ` | |
<h3>${tab.name.charAt(0).toUpperCase() + tab.name.slice(1)} Tab</h3> | |
<p>Content for the ${tab.name} tab.</p> | |
${paramsList} | |
`; | |
} | |
} | |
} | |
// SubTab Handler | |
class SubTabHandler { | |
static load(subtab) { | |
if (!subtab?.name) return; | |
// Update sub-navigation active state | |
document.querySelectorAll('.sub-nav .nav-link').forEach(link => { | |
link.classList.remove('active'); | |
}); | |
const activeLink = document.querySelector(`[data-subtab="${subtab.name}"]`); | |
if (activeLink) activeLink.classList.add('active'); | |
// Update sub-content based on current page | |
const currentPage = Router.current.get('page'); | |
if (currentPage?.name === 'dashboard') { | |
this.loadDashboardSubContent(subtab); | |
} else if (currentPage?.name === 'projects') { | |
this.loadProjectsSubContent(subtab); | |
} | |
} | |
static loadDashboardSubContent(subtab) { | |
const subContent = document.getElementById('dashboardSubContent'); | |
if (!subContent) return; | |
const params = subtab.params ? Array.from(subtab.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div>${params.map(([k, v]) => `<span class="badge">${k}: ${v}</span>`).join('')}</div>` | |
: ''; | |
switch (subtab.name) { | |
case 'analytics': | |
subContent.innerHTML = ` | |
<h4>Analytics</h4> | |
<p>View detailed analytics and metrics.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'chartSettings' } })">Chart Settings</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'analyticsFilters' } })">Filters</button> | |
</div> | |
`; | |
break; | |
case 'reports': | |
subContent.innerHTML = ` | |
<h4>Reports</h4> | |
<p>Generate and view comprehensive reports.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'generateReport' } })">Generate Report</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'reportTemplates' } })">Templates</button> | |
</div> | |
`; | |
break; | |
case 'users': | |
subContent.innerHTML = ` | |
<h4>Users</h4> | |
<p>Manage users and permissions.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'addUser' } })">Add User</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'userFilters' } })">User Filters</button> | |
</div> | |
`; | |
break; | |
default: | |
subContent.innerHTML = ` | |
<h4>${subtab.name.charAt(0).toUpperCase() + subtab.name.slice(1)}</h4> | |
<p>Content for ${subtab.name} section.</p> | |
${paramsList} | |
`; | |
} | |
} | |
static loadProjectsSubContent(subtab) { | |
const subContent = document.getElementById('projectsSubContent'); | |
if (!subContent) return; | |
const params = subtab.params ? Array.from(subtab.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div>${params.map(([k, v]) => `<span class="badge">${k}: ${v}</span>`).join('')}</div>` | |
: ''; | |
switch (subtab.name) { | |
case 'active': | |
subContent.innerHTML = ` | |
<h4>Active Projects</h4> | |
<p>Currently active projects and tasks.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'newProject' } })">New Project</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'projectFilters' } })">Filters</button> | |
</div> | |
`; | |
break; | |
case 'completed': | |
subContent.innerHTML = ` | |
<h4>Completed Projects</h4> | |
<p>Successfully completed projects archive.</p> | |
${paramsList} | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'projectDetails' } })">View Details</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'exportOptions' } })">Export</button> | |
</div> | |
`; | |
break; | |
default: | |
subContent.innerHTML = ` | |
<h4>${subtab.name.charAt(0).toUpperCase() + subtab.name.slice(1)}</h4> | |
<p>Content for ${subtab.name} section.</p> | |
${paramsList} | |
`; | |
} | |
} | |
} | |
// Modal Handler | |
class ModalHandler { | |
static load(modal) { | |
const modalEl = document.getElementById('modal'); | |
if (modal?.name) { | |
document.getElementById('modalContent').innerHTML = this.createContent(modal); | |
modalEl.classList.add('show'); | |
} else { | |
modalEl.classList.remove('show'); | |
} | |
} | |
static createContent(modal) { | |
const params = modal.params ? Array.from(modal.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div>${params.map(([k, v]) => `<span class="badge">${k}: ${v}</span>`).join('')}</div>` | |
: ''; | |
switch (modal.name) { | |
case 'login': | |
return ` | |
<h3>Login</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Email:</label> | |
<input type="email" class="form-control" placeholder="Enter your email"> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Password:</label> | |
<input type="password" class="form-control" placeholder="Enter your password"> | |
</div> | |
${paramsList} | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Login</button> | |
<button type="button" class="btn" onclick="Router.navigate({ modal: { name: 'forgotPassword' } })">Forgot Password?</button> | |
</div> | |
</form> | |
`; | |
case 'editProfile': | |
return ` | |
<h3>Edit Profile</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Name:</label> | |
<input type="text" class="form-control" placeholder="Enter your name"> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Bio:</label> | |
<textarea class="form-control" rows="3" placeholder="Tell us about yourself"></textarea> | |
</div> | |
${paramsList} | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Save Changes</button> | |
<button type="button" class="btn" onclick="Router.navigate({ modal: null })">Cancel</button> | |
</div> | |
</form> | |
`; | |
case 'preferences': | |
return ` | |
<h3>Preferences</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Theme:</label> | |
<select class="form-control"> | |
<option>Light</option> | |
<option>Dark</option> | |
<option>Auto</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Language:</label> | |
<select class="form-control"> | |
<option>English</option> | |
<option>Spanish</option> | |
<option>French</option> | |
</select> | |
</div> | |
${paramsList} | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Save</button> | |
<button type="button" class="btn" onclick="Router.navigate({ offcanvas: { name: 'advancedPrefs' } })">Advanced</button> | |
</div> | |
</form> | |
`; | |
case 'generateReport': | |
return ` | |
<h3>Generate Report</h3> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Report Type:</label> | |
<select class="form-control"> | |
<option>Weekly Summary</option> | |
<option>Monthly Analysis</option> | |
<option>Custom Range</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Format:</label> | |
<select class="form-control"> | |
<option>PDF</option> | |
<option>Excel</option> | |
<option>CSV</option> | |
</select> | |
</div> | |
${paramsList} | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Generate</button> | |
<button type="button" class="btn" onclick="Router.navigate({ offcanvas: { name: 'reportHistory' } })">History</button> | |
</div> | |
</form> | |
`; | |
default: | |
return ` | |
<h3>${modal.name.charAt(0).toUpperCase() + modal.name.slice(1)}</h3> | |
<p>Modal content for ${modal.name}</p> | |
${paramsList} | |
<div class="actions"> | |
<button type="button" class="btn" onclick="Router.navigate({ offcanvas: { name: 'relatedActions' } })">Related Actions</button> | |
</div> | |
`; | |
} | |
} | |
} | |
// Offcanvas Handler | |
class OffcanvasHandler { | |
static load(offcanvas) { | |
const offcanvasEl = document.getElementById('offcanvas'); | |
const backdrop = document.getElementById('offcanvasBackdrop'); | |
if (offcanvas?.name) { | |
document.getElementById('offcanvasContent').innerHTML = this.createContent(offcanvas); | |
offcanvasEl.classList.add('show'); | |
backdrop.classList.add('show'); | |
} else { | |
offcanvasEl.classList.remove('show'); | |
backdrop.classList.remove('show'); | |
} | |
} | |
static createContent(offcanvas) { | |
const params = offcanvas.params ? Array.from(offcanvas.params.entries()) : []; | |
const paramsList = params.length > 0 | |
? `<div>${params.map(([k, v]) => `<div><strong>${k}:</strong> ${v}</div>`).join('')}</div>` | |
: ''; | |
switch (offcanvas.name) { | |
case 'menu': | |
return ` | |
<h4>Navigation Menu</h4> | |
<p>Quick navigation options</p> | |
${paramsList} | |
<hr> | |
<nav> | |
<a href="#page/home" class="nav-link">🏠 Home</a> | |
<a href="#page/dashboard" class="nav-link">📊 Dashboard</a> | |
<a href="#page/projects" class="nav-link">📁 Projects</a> | |
<a href="#tab/profile" class="nav-link">👤 Profile</a> | |
<a href="#tab/settings" class="nav-link">⚙️ Settings</a> | |
</nav> | |
<hr> | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'quickSearch' } })">Quick Search</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'notifications' } })">Notifications</button> | |
</div> | |
`; | |
case 'quickActions': | |
return ` | |
<h4>Quick Actions</h4> | |
<p>Frequently used actions and shortcuts</p> | |
${paramsList} | |
<hr> | |
<div class="actions" style="flex-direction: column;"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'newProject' } })">➕ New Project</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'addUser' } })">👥 Add User</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'generateReport' } })">📈 Generate Report</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'recentItems' } })">🕒 Recent Items</button> | |
</div> | |
`; | |
case 'profileSettings': | |
return ` | |
<h4>Profile Settings</h4> | |
<p>Advanced profile configuration</p> | |
${paramsList} | |
<hr> | |
<div class="actions" style="flex-direction: column;"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'changePassword' } })">🔒 Change Password</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'privacy' } })">🛡️ Privacy Settings</button> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'notifications' } })">🔔 Notifications</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: { name: 'accountSettings' } })">⚙️ Account Settings</button> | |
</div> | |
`; | |
case 'analyticsFilters': | |
return ` | |
<h4>Analytics Filters</h4> | |
<p>Filter and customize your analytics view</p> | |
${paramsList} | |
<hr> | |
<form> | |
<div class="form-group"> | |
<label class="form-label">Date Range:</label> | |
<select class="form-control"> | |
<option>Last 7 days</option> | |
<option>Last 30 days</option> | |
<option>Last 3 months</option> | |
<option>Custom range</option> | |
</select> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Metrics:</label> | |
<select class="form-control" multiple> | |
<option>Page Views</option> | |
<option>Unique Visitors</option> | |
<option>Conversion Rate</option> | |
<option>Revenue</option> | |
</select> | |
</div> | |
<div class="actions"> | |
<button type="button" class="btn btn-primary">Apply Filters</button> | |
<button type="button" class="btn" onclick="Router.navigate({ modal: { name: 'saveFilter' } })">Save Filter</button> | |
</div> | |
</form> | |
`; | |
case 'notifications': | |
return ` | |
<h4>Notifications</h4> | |
<p>Recent notifications and alerts</p> | |
${paramsList} | |
<hr> | |
<div style="max-height: 300px; overflow-y: auto;"> | |
<div style="padding: 10px; border-bottom: 1px solid #eee;"> | |
<strong>New Project Created</strong><br> | |
<small>2 minutes ago</small> | |
</div> | |
<div style="padding: 10px; border-bottom: 1px solid #eee;"> | |
<strong>Report Generated</strong><br> | |
<small>1 hour ago</small> | |
</div> | |
<div style="padding: 10px; border-bottom: 1px solid #eee;"> | |
<strong>User Added</strong><br> | |
<small>3 hours ago</small> | |
</div> | |
</div> | |
<hr> | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'notificationSettings' } })">Settings</button> | |
<button class="btn">Mark All Read</button> | |
</div> | |
`; | |
default: | |
return ` | |
<h4>${offcanvas.name.charAt(0).toUpperCase() + offcanvas.name.slice(1)}</h4> | |
<p>Offcanvas content for ${offcanvas.name}</p> | |
${paramsList} | |
<hr> | |
<div class="actions"> | |
<button class="btn" onclick="Router.navigate({ modal: { name: 'details' } })">View Details</button> | |
<button class="btn" onclick="Router.navigate({ offcanvas: null })">Close</button> | |
</div> | |
`; | |
} | |
} | |
} | |
// Router - Now focused only on routing logic | |
class Router { | |
static current = new Map(); | |
static previous = new Map(); | |
static init() { | |
this.bindEvents(); | |
this.handleRoute(); | |
console.log('Router initialized'); | |
} | |
static bindEvents() { | |
window.addEventListener('hashchange', () => this.handleRoute()); | |
window.addEventListener('popstate', () => this.handleRoute()); | |
// Handle navigation clicks | |
document.addEventListener('click', (e) => { | |
if (e.target.matches('[data-tab]')) { | |
e.preventDefault(); | |
const tab = e.target.dataset.tab; | |
this.navigate({ tab: { name: tab } }); | |
} | |
if (e.target.matches('[data-subtab]')) { | |
e.preventDefault(); | |
const subtab = e.target.dataset.subtab; | |
const currentPage = this.current.get('page'); | |
this.navigate({ | |
page: currentPage, | |
subtab: { name: subtab } | |
}); | |
} | |
}); | |
} | |
static parseHash() { | |
const hash = location.hash.slice(1); | |
if (!hash) return new Map(); | |
const segments = hash.split('/').filter(Boolean); | |
const route = new Map(); | |
for (let i = 0; i < segments.length; i += 2) { | |
const type = segments[i]; | |
const segment = segments[i + 1] || ''; | |
const [name, ...paramParts] = segment.split(';'); | |
if (name) { | |
const params = new Map(); | |
paramParts.forEach(p => { | |
const [k, v] = p.split('='); | |
if (k && v !== undefined) { | |
params.set(k, decodeURIComponent(v)); | |
} | |
}); | |
route.set(type, { name, params }); | |
} | |
} | |
return route; | |
} | |
static handleRoute() { | |
try { | |
const route = this.parseHash(); | |
const prev = new Map(this.current); | |
this.previous = prev; | |
// Delegate to specific handlers | |
if (!this.routesEqual(route.get('page'), prev.get('page'))) { | |
PageHandler.load(route.get('page')); | |
} | |
if (!this.routesEqual(route.get('tab'), prev.get('tab'))) { | |
TabHandler.load(route.get('tab')); | |
} | |
if (!this.routesEqual(route.get('subtab'), prev.get('subtab'))) { | |
SubTabHandler.load(route.get('subtab')); | |
} | |
if (!this.routesEqual(route.get('offcanvas'), prev.get('offcanvas'))) { | |
OffcanvasHandler.load(route.get('offcanvas')); | |
} | |
if (!this.routesEqual(route.get('modal'), prev.get('modal'))) { | |
ModalHandler.load(route.get('modal')); | |
} | |
this.current = route; | |
this.updateStatus(); | |
} catch (error) { | |
console.error('Route handling error:', error); | |
} | |
} | |
static navigate(layers) { | |
try { | |
const newRoute = new Map(this.current); | |
Object.entries(layers).forEach(([type, data]) => { | |
if (data === null || data === undefined) { | |
newRoute.delete(type); | |
} else if (data.name) { | |
newRoute.set(type, { | |
name: data.name, | |
params: new Map(Object.entries(data.params || {})) | |
}); | |
} | |
}); | |
const newHash = this.buildHash(newRoute); | |
if (location.hash === newHash) { | |
this.handleRoute(); | |
} else { | |
location.hash = newHash; | |
} | |
} catch (error) { | |
console.error('Navigation error:', error); | |
} | |
} | |
static buildHash(route) { | |
const parts = []; | |
for (const [type, data] of route) { | |
if (data?.name) { | |
const paramStr = Array.from(data.params || []) | |
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`) | |
.join(';'); | |
parts.push(type, `${data.name}${paramStr ? ';' + paramStr : ''}`); | |
} | |
} | |
return '#' + parts.join('/'); | |
} | |
static routesEqual(route1, route2) { | |
if (!route1 && !route2) return true; | |
if (!route1 || !route2) return false; | |
if (route1.name !== route2.name) return false; | |
const params1 = route1.params || new Map(); | |
const params2 = route2.params || new Map(); | |
if (params1.size !== params2.size) return false; | |
for (const [key, value] of params1) { | |
if (params2.get(key) !== value) return false; | |
} | |
return true; | |
} | |
static updateStatus() { | |
const activeRoutes = Array.from(this.current.keys()); | |
document.getElementById('statusIndicator').textContent = | |
activeRoutes.length > 0 ? `Active: ${activeRoutes.join(', ')}` : 'Ready'; | |
} | |
static clearAll() { | |
this.navigate({ | |
page: null, | |
tab: null, | |
subtab: null, | |
modal: null, | |
offcanvas: null | |
}); | |
} | |
} | |
// Close modal when clicking backdrop | |
function closeModalOnBackdrop(event) { | |
if (event.target === event.currentTarget) { | |
Router.navigate({ modal: null }); | |
} | |
} | |
// Initialize router | |
document.addEventListener('DOMContentLoaded', () => { | |
Router.init(); | |
}); | |
// Global access | |
window.Router = Router; | |
window.PageHandler = PageHandler; | |
window.TabHandler = TabHandler; | |
window.SubTabHandler = SubTabHandler; | |
window.ModalHandler = ModalHandler; | |
window.OffcanvasHandler = OffcanvasHandler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment