import { useSearchParams } from '@rspress/core/runtime'; import clsx from 'clsx'; import { Children, type ForwardedRef, forwardRef, isValidElement, type ReactElement, type ReactNode, useEffect, useMemo, } from 'react'; import './index.scss'; type PageTabItem = { label?: string | ReactNode; content?: ReactNode; }; function getTabValuesFromChildren( children: ReactElement[], ): PageTabItem[] { return Children.map>( children, (child, index) => { if (isValidElement(child)) { return { label: child.props?.label || undefined, content: children[index], } satisfies PageTabItem; } return { label: index, content: children[index], } satisfies PageTabItem; }, ); } export interface PageTabsProps { values?: ReactNode[] | ReadonlyArray | PageTabItem[]; /** * determine the query parameter name for the current tab * @default 'page'' */ id?: string; children: ReactNode; className?: string; tabPosition?: 'left' | 'center'; } function usePageTabs(id: string, children: ReactElement[]) { const [searchParams, setSearchParams] = useSearchParams(); const tabValues = useMemo(() => { return getTabValuesFromChildren(children); }, [children]); function navigateToTab(index: number) { if (index === 0) { searchParams.delete(id); } else { searchParams.set(id, String(index)); } setSearchParams(searchParams); } useEffect(() => { const currIndex = Number(searchParams.get(id) ?? '0'); if (!Number.isNaN(currIndex)) { document.body.dataset.pageTabsActiveIndex = currIndex.toString(); } }, [searchParams]); const injectScript = `(function () { var searchParams = new URLSearchParams(window.location.search); var currIndex = Number(searchParams.get('${id}') || 0); if (!Number.isNaN(currIndex)) { document.body.dataset.pageTabsActiveIndex = currIndex; } })();`; return { tabValues, navigateToTab, injectScript, }; } let renderCountForTocUpdate = 0; export const PageTabs = forwardRef( (props: PageTabsProps, ref: ForwardedRef): ReactElement => { const { children: rawChildren, tabPosition = 'left', className, id = 'page', } = props; // remove "\n" character when write JSX element in multiple lines, use Children.toArray for Tabs with no Tab element const children = Children.toArray(rawChildren).filter( child => !(typeof child === 'string' && child.trim() === '') && isValidElement(child), ) as unknown as ReactElement[]; const { tabValues, navigateToTab, injectScript } = usePageTabs( id, children, ); renderCountForTocUpdate++; return ( <> {/* First screen during SSR */}