Files
Sankofa/src/lib/accessibility.ts
T
defiQUG 6f28146ac3 Initial Phoenix Sankofa Cloud setup
- Complete project structure with Next.js frontend
- GraphQL API backend with Apollo Server
- Portal application with NextAuth
- Crossplane Proxmox provider
- GitOps configurations
- CI/CD pipelines
- Testing infrastructure (Vitest, Jest, Go tests)
- Error handling and monitoring
- Security hardening
- UI component library
- Documentation
2025-11-28 12:54:33 -08:00

94 lines
2.3 KiB
TypeScript

/**
* Accessibility utilities
*/
/**
* Get accessible label for an element
*/
export function getAccessibleLabel(
label?: string,
placeholder?: string,
fallback = 'Interactive element'
): string {
return label || placeholder || fallback
}
/**
* Generate unique ID for form elements
*/
export function generateId(prefix = 'id'): string {
return `${prefix}-${Math.random().toString(36).substring(2, 9)}`
}
/**
* Check if element is focusable
*/
export function isFocusable(element: HTMLElement): boolean {
const focusableSelectors = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
].join(', ')
return element.matches(focusableSelectors)
}
/**
* Trap focus within a container
*/
export function trapFocus(container: HTMLElement): () => void {
const focusableElements = container.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
)
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
const handleTab = (e: KeyboardEvent) => {
if (e.key !== 'Tab') {
return
}
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault()
lastElement?.focus()
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault()
firstElement?.focus()
}
}
}
container.addEventListener('keydown', handleTab)
// Return cleanup function
return () => {
container.removeEventListener('keydown', handleTab)
}
}
/**
* Announce message to screen readers
*/
export function announceToScreenReader(message: string, priority: 'polite' | 'assertive' = 'polite') {
const announcement = document.createElement('div')
announcement.setAttribute('role', 'status')
announcement.setAttribute('aria-live', priority)
announcement.setAttribute('aria-atomic', 'true')
announcement.className = 'sr-only'
announcement.textContent = message
document.body.appendChild(announcement)
setTimeout(() => {
document.body.removeChild(announcement)
}, 1000)
}