All files / src/components/layout/SkipLinks index.tsx

0% Statements 0/106
100% Branches 0/0
0% Functions 0/1
0% Lines 0/106

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;