import { createContext, ReactElement, useCallback, useEffect, useState, Children, isValidElement, } from 'react'; import { matchPath, Outlet, Route, Routes, useLocation, useNavigate, useRoutes, } from 'react-router'; import styled from 'styled-components'; import { ButtonIcon } from '../buttonv2/Buttonv2.component'; import { BasicText, EmphaseText, SecondaryText } from '../text/Text.component'; import { ScrollButton } from './ScrollButton'; import { ScrollableContainer, TabBar, TabContent, TabItem, TabsContainer, TabsScroller, } from './StyledTabs'; import { Query, Tab, TabProps } from './Tab'; import { useScrollingTabs } from './useScrollingTabs'; type TabsProps = { activeTabColor?: string; activeTabSeparator?: string; tabLineColor?: string; inactiveTabColor?: string; tabContentColor?: string; separatorColor?: string; tabHoverColor?: string; children: ReactElement[]; className?: string; }; export const TabsContext = createContext(false); const TabIcon = styled(ButtonIcon)` color: ${(props) => props.theme.textSecondary}; `; function Tabs({ activeTabColor, activeTabSeparator, tabLineColor, inactiveTabColor, tabContentColor, separatorColor, tabHoverColor, children, className, ...rest }: TabsProps) { const location = useLocation(); const navigate = useNavigate(); const url = location.pathname; const [selectedTabIndex, setSelectedTabIndex] = useState< number | null | undefined >(null); const queryURL = new URLSearchParams(location.search); const filteredTabsChildren: ReactElement[] = Children.toArray( children, ).filter( (child) => isValidElement(child) && child.type === Tab, ) as ReactElement[]; const matchQuery = useCallback( (query: Query): boolean => { for (const key of Object.keys(query)) { // To support the case of {tab:null} if (queryURL.get(key) === null && !query[key]) { return true; } if (!(queryURL.has(key) && queryURL.get(key) === query[key])) return false; } return true; }, [queryURL], ); const serialize = (query?: Query): string => { if (!query) { return ''; } else { const o = Object.fromEntries( Object.entries(query).filter(([_, v]) => v != null), ); //$FlowFixMe return '?' + new URLSearchParams(o).toString(); } }; const getPushHistoryPath = (path: string, query?: Query): string => { const sanitizedSegment = path.replace(/^\/+/, ''); const replaceUrl = location.pathname.replace(/[^/]+$/, sanitizedSegment); if (path.startsWith('/')) { return `${replaceUrl}${serialize(query)}`; } return `${path}${serialize(query)}`; }; useEffect(() => { let hasSelectedTab = false; filteredTabsChildren.forEach((child, index) => { const isSelected = !!matchPath( (child.props.path.startsWith('/') ? child.props.path : url + '/' + child.props.path) + '*', location.pathname, ) && (child.props.query ? matchQuery(child.props.query) : true); if (isSelected) { setSelectedTabIndex(index); hasSelectedTab = true; } }); if (!hasSelectedTab) setSelectedTabIndex(null); }, [location.pathname, filteredTabsChildren, matchQuery]); const { scrollButtonEndRef, scrollButtonStartRef, tabsListRef, tabsRef, handleStartScrollClick, handleEndScrollClick, handleTabsScroll, handleKeyDown, displayScroll, } = useScrollingTabs(selectedTabIndex); const tabItems = filteredTabsChildren.map((child, index) => { const { path, query, label, textBadge, children, icon, ...childRest }: TabProps = child.props; const isSelected = selectedTabIndex === index; const realPath = path.startsWith('/') ? `/${path.split('/').pop()}` : path; return ( navigate(getPushHistoryPath(realPath, query))} selected={isSelected} tabHoverColor={tabHoverColor} inactiveTabColor={inactiveTabColor} activeTabColor={activeTabColor} activeTabSeparator={activeTabSeparator} tabIndex={isSelected ? 0 : -1} onKeyDown={(event) => { if ( event.key === ' ' || event.key === 'Enter' || event.key === 'Spacebar' ) { event.preventDefault(); navigate(getPushHistoryPath(realPath, query)); } }} {...childRest} > {icon && {icon}} {isSelected ? ( {label} ) : ( {label} )} {textBadge && {textBadge}} ); }); return ( {displayScroll.start && ( )} {tabItems} {displayScroll.end && ( )} {filteredTabsChildren.map((tab, index) => { const path = tab.props.path.split('/').pop(); if (tab.props.query && !matchQuery(tab.props.query)) { return <>; } return ( {tab.props.children} } /> ); })} ); } Tabs.Tab = Tab; // re-export Tab export { Tab, Tabs };