feat: adds sidebar as overlay and moves navbar content there
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
bb6ea0085b
commit
b0097ab99d
4 changed files with 316 additions and 100 deletions
167
assets/js/app.js
167
assets/js/app.js
|
|
@ -102,3 +102,170 @@ liveSocket.connect()
|
|||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
// Sidebar accessibility improvements
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const drawerToggle = document.getElementById("main-drawer")
|
||||
const sidebarToggle = document.getElementById("sidebar-toggle")
|
||||
const sidebar = document.getElementById("main-sidebar")
|
||||
|
||||
if (!drawerToggle || !sidebarToggle || !sidebar) return
|
||||
|
||||
// Manage tabindex for sidebar elements based on open/closed state
|
||||
const updateSidebarTabIndex = (isOpen) => {
|
||||
// Find all potentially focusable elements (including those with tabindex="-1")
|
||||
const allFocusableElements = sidebar.querySelectorAll(
|
||||
'a[href], button, select, input:not([type="hidden"]), [tabindex]'
|
||||
)
|
||||
|
||||
allFocusableElements.forEach(el => {
|
||||
// Skip the overlay button
|
||||
if (el.closest('.drawer-overlay')) return
|
||||
|
||||
if (isOpen) {
|
||||
// Remove tabindex="-1" to make focusable when open
|
||||
if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') === '-1') {
|
||||
el.removeAttribute('tabindex')
|
||||
}
|
||||
} else {
|
||||
// Set tabindex="-1" to remove from tab order when closed
|
||||
if (!el.hasAttribute('tabindex')) {
|
||||
el.setAttribute('tabindex', '-1')
|
||||
} else if (el.getAttribute('tabindex') !== '-1') {
|
||||
// Store original tabindex in data attribute before setting to -1
|
||||
if (!el.hasAttribute('data-original-tabindex')) {
|
||||
el.setAttribute('data-original-tabindex', el.getAttribute('tabindex'))
|
||||
}
|
||||
el.setAttribute('tabindex', '-1')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Find first focusable element in sidebar
|
||||
// Priority: first navigation link (menuitem) > other links > other focusable elements
|
||||
const getFirstFocusableElement = () => {
|
||||
// First, try to find the first navigation link (menuitem)
|
||||
const firstNavLink = sidebar.querySelector('a[href][role="menuitem"]:not([tabindex="-1"])')
|
||||
if (firstNavLink && !firstNavLink.closest('.drawer-overlay')) {
|
||||
return firstNavLink
|
||||
}
|
||||
|
||||
// Fallback: any navigation link
|
||||
const firstLink = sidebar.querySelector('a[href]:not([tabindex="-1"])')
|
||||
if (firstLink && !firstLink.closest('.drawer-overlay')) {
|
||||
return firstLink
|
||||
}
|
||||
|
||||
// Last resort: any other focusable element
|
||||
const focusableSelectors = [
|
||||
'button:not([tabindex="-1"]):not([disabled])',
|
||||
'select:not([tabindex="-1"]):not([disabled])',
|
||||
'input:not([tabindex="-1"]):not([disabled]):not([type="hidden"])',
|
||||
'[tabindex]:not([tabindex="-1"])'
|
||||
]
|
||||
|
||||
for (const selector of focusableSelectors) {
|
||||
const element = sidebar.querySelector(selector)
|
||||
if (element && !element.closest('.drawer-overlay')) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Update aria-expanded when drawer state changes
|
||||
const updateAriaExpanded = () => {
|
||||
const isOpen = drawerToggle.checked
|
||||
sidebarToggle.setAttribute("aria-expanded", isOpen.toString())
|
||||
|
||||
// Update dropdown aria-expanded if present
|
||||
const userMenuButton = sidebar.querySelector('button[aria-haspopup="true"]')
|
||||
if (userMenuButton) {
|
||||
const dropdown = userMenuButton.closest('.dropdown')
|
||||
const isDropdownOpen = dropdown?.classList.contains('dropdown-open')
|
||||
if (userMenuButton) {
|
||||
userMenuButton.setAttribute("aria-expanded", (isDropdownOpen || false).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for changes to the drawer checkbox
|
||||
drawerToggle.addEventListener("change", () => {
|
||||
const isOpen = drawerToggle.checked
|
||||
updateAriaExpanded()
|
||||
updateSidebarTabIndex(isOpen)
|
||||
if (!isOpen) {
|
||||
// When closing, return focus to toggle button
|
||||
sidebarToggle.focus()
|
||||
}
|
||||
})
|
||||
|
||||
// Update on initial load
|
||||
updateAriaExpanded()
|
||||
updateSidebarTabIndex(drawerToggle.checked)
|
||||
|
||||
// Close sidebar with ESC key
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && drawerToggle.checked) {
|
||||
drawerToggle.checked = false
|
||||
updateAriaExpanded()
|
||||
updateSidebarTabIndex(false)
|
||||
// Return focus to toggle button
|
||||
sidebarToggle.focus()
|
||||
}
|
||||
})
|
||||
|
||||
// Improve keyboard navigation for sidebar toggle
|
||||
sidebarToggle.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault()
|
||||
const wasOpen = drawerToggle.checked
|
||||
drawerToggle.checked = !drawerToggle.checked
|
||||
updateAriaExpanded()
|
||||
|
||||
// If opening, move focus to first element in sidebar
|
||||
if (!wasOpen && drawerToggle.checked) {
|
||||
updateSidebarTabIndex(true)
|
||||
// Use setTimeout to ensure DOM is updated
|
||||
setTimeout(() => {
|
||||
const firstElement = getFirstFocusableElement()
|
||||
if (firstElement) {
|
||||
firstElement.focus()
|
||||
}
|
||||
}, 50)
|
||||
} else if (wasOpen && !drawerToggle.checked) {
|
||||
updateSidebarTabIndex(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Also handle click events to update tabindex and focus
|
||||
sidebarToggle.addEventListener("click", () => {
|
||||
setTimeout(() => {
|
||||
const isOpen = drawerToggle.checked
|
||||
updateSidebarTabIndex(isOpen)
|
||||
if (isOpen) {
|
||||
const firstElement = getFirstFocusableElement()
|
||||
if (firstElement) {
|
||||
firstElement.focus()
|
||||
}
|
||||
}
|
||||
}, 50)
|
||||
})
|
||||
|
||||
// Handle dropdown keyboard navigation
|
||||
const userMenuButton = sidebar?.querySelector('button[aria-haspopup="true"]')
|
||||
if (userMenuButton) {
|
||||
userMenuButton.addEventListener("click", () => {
|
||||
setTimeout(updateAriaExpanded, 0)
|
||||
})
|
||||
|
||||
userMenuButton.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault()
|
||||
userMenuButton.click()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue