'use client'; import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { type Key, Tab, TabList, TabPanel, Tabs, type TabsProps } from 'react-aria-components'; import { useEventCallback } from 'usehooks-ts'; import { getOrCreateStoreByKey } from './getOrCreateStoreByKey'; export type TabItem = { key: Key; label: string; body: React.ReactNode; footer?: React.ReactNode; }; type OpenAPITabsContextData = { items: TabItem[]; selectedTab: TabItem | null; }; const OpenAPITabsContext = createContext(null); function useOpenAPITabsContext() { const context = useContext(OpenAPITabsContext); if (!context) { throw new Error('OpenAPITabsContext is missing'); } return context; } /** * The OpenAPI Tabs wrapper component. */ export function OpenAPITabs( props: React.PropsWithChildren ) { const { children, items, stateKey } = props; const [tabKey, setTabKey] = useState(() => { if (stateKey && typeof window !== 'undefined') { const store = getOrCreateStoreByKey(stateKey); const tabKey = store.getState().key; if (tabKey) { return tabKey; } } return items[0]?.key ?? null; }); const selectTab = useEventCallback((key: Key | null) => { if (!key || key === tabKey) { return; } const tab = items.find((item) => item.key === key); if (!tab) { return; } setTabKey(key); }); const selectedTab = items.find((item) => item.key === tabKey) ?? items[0] ?? null; const cancelDeferRef = useRef<(() => void) | null>(null); useEffect(() => { if (!stateKey) { return undefined; } const store = getOrCreateStoreByKey(stateKey); return store.subscribe((state) => { cancelDeferRef.current?.(); cancelDeferRef.current = defer(() => selectTab(state.key)); }); }, [stateKey, selectTab]); useEffect(() => { return () => cancelDeferRef.current?.(); }, []); const contextValue = useMemo(() => ({ items, selectedTab }), [items, selectedTab]); return ( { selectTab(tabKey); if (stateKey) { const store = getOrCreateStoreByKey(stateKey); store.setState({ key: tabKey }); } }} selectedKey={tabKey} > {children} ); } const defer = (fn: () => void) => { const id = setTimeout(fn, 0); return () => clearTimeout(id); }; /** * The OpenAPI Tabs list component. * This component should be used as a child of the OpenAPITabs component. * It renders the list of tabs. */ export function OpenAPITabsList() { const { items } = useOpenAPITabsContext(); return ( {items.map((tab) => ( ({ outline: isFocusVisible ? '2px solid rgb(var(--primary-color-500)/0.4)' : 'none', })} className="openapi-tabs-tab" > {tab.label} ))} ); } /** * The OpenAPI Tabs panels component. * This component should be used as a child of the OpenAPITabs component. * It renders the content of the selected tab. */ export function OpenAPITabsPanels() { const { selectedTab } = useOpenAPITabsContext(); if (!selectedTab) { return null; } const key = selectedTab.key.toString(); return (
{selectedTab.body}
{selectedTab.footer ? (
{selectedTab.footer}
) : null}
); }