import React, { useState, useRef, useCallback } from 'react'; import { useScreenSize } from '@digigov/ui/utils/hooks/useScreen'; export type TabId = string | number | null; export interface UseTabsProps { defaultOpen?: TabId; } export interface UseTabsReturn { register: (id: TabId) => { role: string; href: string; id: string; 'aria-selected': boolean; 'aria-controls': string; tabIndex: number; onClick: (e: any) => void; onKeyDown: (e: React.KeyboardEvent) => void; ref?: React.RefObject; }; panel: (id: TabId) => { tab: TabId; role: string; 'aria-labelledby': string; id: string; active: boolean; }; currentOpen: TabId; open: (id: TabId) => void; } export const useTabs = ({ defaultOpen = null, }: UseTabsProps = {}): UseTabsReturn => { const [currentOpen, setCurrentOpen] = useState(defaultOpen); const tabsRef = useRef>>( new Map() ); const { screenSize } = useScreenSize(); const open = useCallback((id: TabId) => { setCurrentOpen(id); tabsRef.current.get(id)?.current?.focus(); }, []); const navigate = useCallback( (direction: 'next' | 'previous') => { const focusedTabId = document.activeElement?.id.match(/tab-(.*?)$/)?.[1]; const tabs = Array.from(tabsRef.current.keys()); const currentIndex = tabs.indexOf(focusedTabId || currentOpen); const newIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1; if (newIndex >= 0 && newIndex < tabs.length) { tabsRef.current.get(tabs[newIndex])?.current?.focus(); } }, [currentOpen, tabsRef.current] ); const register = useCallback( (id: TabId) => { if (!tabsRef.current.has(id)) { if (tabsRef.current.size === 0 && !currentOpen) { setCurrentOpen(id); } tabsRef.current.set(id, React.createRef()); } return { role: 'tab', id: `tab-${id}`, href: `#panel-${id}`, 'aria-selected': currentOpen === id, open: currentOpen === id, 'aria-controls': `panel-${id}`, tabIndex: currentOpen === id ? 0 : -1, onClick: (e) => { // If the screen is mobile, we don't want to prevent the default behavior of the anchor tag, // which is to navigate to the href. if (screenSize != 'xs' && screenSize != 'sm') { e.preventDefault(); } open(id); }, onKeyDown: (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowRight': case 'ArrowDown': navigate('next'); break; case 'ArrowLeft': case 'ArrowUp': navigate('previous'); break; case 'Enter': open(id); break; } }, ref: tabsRef.current.get(id), }; }, [currentOpen] ); const panel = (id: TabId) => { return { role: 'tabpanel', 'aria-labelledby': `tab-${id}`, id: `panel-${id}`, active: currentOpen === id, tab: id, }; }; return { register, panel, currentOpen, open, }; };