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 108 109 110 111 112 | 129x 129x 129x 129x 268x 129x 70x 36x 33x 33x 129x 26x 1x 25x 25x 5x 5x 5x 12x 12x 12x 3x 3x 3x 3x 3x 3x 2x 23x 23x 23x 129x | /**
* Custom hook for managing tab state and keyboard navigation
*
* Provides reusable tab state management with full keyboard navigation support
* (Arrow keys, Home, End) and accessibility features.
*
* @module hooks/useTabs
*/
import { useState, useCallback, useRef, useMemo } from 'react';
import { Tab, UseTabsOptions, UseTabsReturn } from '../types/tabs';
/**
* Hook for managing tab state with keyboard navigation
*
* This hook provides:
* - Tab selection state management
* - Keyboard navigation (Arrow Left/Right, Home, End)
* - Focus management for tab buttons
* - Support for disabled tabs
*
* @param tabs - Array of tab configurations
* @param options - Optional configuration for initial tab and change callback
* @returns Tab state and handlers
*
* @example
* ```tsx
* const tabs: Tab[] = [
* { id: 'tab1', label: 'First Tab', content: <div>Content 1</div> },
* { id: 'tab2', label: 'Second Tab', content: <div>Content 2</div> },
* ];
*
* const { activeTab, selectTab, handleKeyDown, tabRefs } = useTabs(tabs, {
* initialTab: 'tab1',
* onChange: (tabId) => console.log('Tab changed to:', tabId),
* });
* ```
*/
export function useTabs(tabs: Tab[], options: UseTabsOptions = {}): UseTabsReturn {
const { initialTab, onChange } = options;
const [activeTab, setActiveTab] = useState<string>(initialTab || tabs[0]?.id || '');
const tabRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
// Memoize non-disabled tab IDs to avoid recalculating on every keydown
const enabledTabIds = useMemo(() => {
return tabs.filter(t => !t.disabled).map(t => t.id);
}, [tabs]);
/**
* Select a tab programmatically
* Ignores disabled tabs and triggers onChange callback
*/
const selectTab = useCallback((tabId: string): void => {
const tab = tabs.find(t => t.id === tabId);
if (tab && !tab.disabled) {
setActiveTab(tabId);
onChange?.(tabId);
}
}, [tabs, onChange]);
/**
* Handle keyboard navigation for tabs
* Supports Arrow Left/Right, Home, and End keys
*/
const handleKeyDown = useCallback((event: React.KeyboardEvent, currentTabId: string): void => {
// Early return if no enabled tabs
if (enabledTabIds.length === 0) {
return;
}
const currentIndex = enabledTabIds.indexOf(currentTabId);
let newIndex: number;
switch (event.key) {
case 'ArrowLeft':
event.preventDefault();
// Wrap to end if at beginning
newIndex = currentIndex > 0 ? currentIndex - 1 : enabledTabIds.length - 1;
break;
case 'ArrowRight':
event.preventDefault();
// Wrap to beginning if at end
newIndex = currentIndex < enabledTabIds.length - 1 ? currentIndex + 1 : 0;
break;
case 'Home':
event.preventDefault();
newIndex = 0;
break;
case 'End':
event.preventDefault();
newIndex = enabledTabIds.length - 1;
break;
default:
// Don't handle other keys
return;
}
const newTabId = enabledTabIds[newIndex];
selectTab(newTabId);
// Focus the new tab button
tabRefs.current.get(newTabId)?.focus();
}, [enabledTabIds, selectTab]);
return {
activeTab,
selectTab,
handleKeyDown,
tabRefs,
};
}
|