Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 'use client'; import { cn } from '@/lib/core'; /** * Skip link configuration */ export interface SkipLink { /** Target element ID (without #) */ targetId: string; /** Display text for the link */ label: string; } /** * Default skip links for the application */ const DEFAULT_SKIP_LINKS: SkipLink[] = [ { targetId: 'main-content', label: 'Skip to main content' }, { targetId: 'main-navigation', label: 'Skip to navigation' }, ]; export interface SkipLinksProps { /** Custom skip links (defaults to main content and navigation) */ links?: SkipLink[]; /** Additional CSS classes */ className?: string; } /** * SkipLinks Component * * Provides keyboard-accessible skip navigation links for screen readers * and keyboard users. Hidden until focused via Tab key. * * These links allow users to bypass repetitive navigation and jump * directly to main content sections. * * @example * ```tsx * // In layout.tsx (before header): * <SkipLinks /> * * // Then in your page/content: * <main id="main-content">...</main> * <nav id="main-navigation">...</nav> * ``` * * @example * ```tsx * // Custom skip links: * <SkipLinks * links={[ * { targetId: 'main-content', label: 'Skip to main content' }, * { targetId: 'search', label: 'Skip to search' }, * { targetId: 'footer', label: 'Skip to footer' }, * ]} * /> * ``` */ export function SkipLinks({ links = DEFAULT_SKIP_LINKS, className }: SkipLinksProps) { return ( <nav aria-label="Skip links" className={cn('skip-links', className)} > {links.map(({ targetId, label }) => ( <a key={targetId} href={`#${targetId}`} className={cn( // Hidden by default 'sr-only', // Show on focus 'focus:not-sr-only', 'focus:absolute', 'focus:top-4', 'focus:left-4', 'focus:z-[9999]', // Styling 'focus:px-4', 'focus:py-2', 'focus:rounded-md', 'focus:bg-white', 'focus:text-gray-900', 'focus:shadow-lg', 'focus:ring-2', 'focus:ring-blue', 'focus:ring-offset-2', // Dark mode 'dark:focus:bg-gray-800', 'dark:focus:text-gray-100', 'dark:focus:ring-offset-gray-900', // Typography 'focus:font-medium', 'focus:text-sm' )} > {label} </a> ))} </nav> ); } export default SkipLinks; |