All files / packages/design-system-2-site/src/components InlineTabsHydrator.tsx

95.74% Statements 45/47
69.56% Branches 16/23
100% Functions 8/8
100% Lines 43/43

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 762x                       6x 6x 6x 18x 18x 18x 18x 18x 18x         5x 4x 7x   4x 1x 1x     4x 6x 6x 6x 6x 6x   6x   6x   1x 1x   2x 2x   1x 1x   1x 1x   1x   5x 5x 5x 5x     4x 4x 4x 4x 4x       5x    
'use client';
 
import { useEffect } from 'react';
 
/**
 * Wires up the `::::tabs` directive (rendered at build time as an ARIA
 * tablist, first panel visible). Clicking a tab or moving with the arrow
 * keys / Home / End selects it and shows its panel (automatic activation, per
 * the APG tabs pattern). Listeners are delegated on `document`, so tabs
 * injected after client-side navigation work too.
 */
function select(tab: HTMLElement) {
  const list = tab.closest('[role="tablist"]');
  Iif (!list) return;
  list.querySelectorAll<HTMLElement>('[role="tab"]').forEach((t) => {
    const selected = t === tab;
    t.setAttribute('aria-selected', String(selected));
    t.tabIndex = selected ? 0 : -1;
    const panelId = t.getAttribute('aria-controls');
    const panel = panelId ? document.getElementById(panelId) : null;
    Eif (panel) panel.hidden = !selected;
  });
}
 
export function InlineTabsHydrator() {
  useEffect(() => {
    const tabFrom = (e: Event) =>
      e.target instanceof Element ? e.target.closest<HTMLElement>('.inline-tabs__tab') : null;
 
    const onClick = (e: Event) => {
      const tab = tabFrom(e);
      Eif (tab) select(tab);
    };
 
    const onKeyDown = (e: KeyboardEvent) => {
      const tab = tabFrom(e);
      const list = tab?.closest<HTMLElement>('[role="tablist"]');
      Iif (!tab || !list) return;
      const tabs = Array.from(list.querySelectorAll<HTMLElement>('[role="tab"]'));
      const index = tabs.indexOf(tab);
      // Arrow keys follow the reading direction.
      const step = getComputedStyle(list).direction === 'rtl' ? -1 : 1;
      let next: number;
      switch (e.key) {
        case 'ArrowRight':
          next = index + step;
          break;
        case 'ArrowLeft':
          next = index - step;
          break;
        case 'Home':
          next = 0;
          break;
        case 'End':
          next = tabs.length - 1;
          break;
        default:
          return;
      }
      e.preventDefault();
      const target = tabs[(next + tabs.length) % tabs.length];
      select(target);
      target.focus();
    };
 
    document.addEventListener('click', onClick);
    document.addEventListener('keydown', onKeyDown);
    return () => {
      document.removeEventListener('click', onClick);
      document.removeEventListener('keydown', onKeyDown);
    };
  }, []);
 
  return null;
}