'use client'; import classNames from 'classnames'; import React, { useCallback } from 'react'; interface InteractiveSectionTab { key: string; label: string; body: React.ReactNode; } let globalState: Record = {}; const listeners = new Set<() => void>(); function useSyncedTabsGlobalState() { const subscribe = useCallback((callback: () => void) => { listeners.add(callback); return () => listeners.delete(callback); }, []); const getSnapshot = useCallback(() => globalState, []); const setSyncedTabs = useCallback( (updater: (tabs: Record) => Record) => { globalState = updater(globalState); listeners.forEach((listener) => listener()); }, [], ); const tabs = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot); return [tabs, setSyncedTabs] as const; } /** * To optimize rendering, most of the components are server-components, * and the interactiveness is mainly handled by a few key components like this one. */ export function InteractiveSection(props: { id?: string; /** Class name to be set on the section, sub-elements will use it as prefix */ className: string; /** If true, the content can be toggeable */ toggeable?: boolean; /** Default state of the toggle */ defaultOpened?: boolean; /** Icons to display for the toggle */ toggleOpenIcon?: React.ReactNode; toggleCloseIcon?: React.ReactNode; /** Tabs of content to display */ tabs?: Array; /** Default tab to have opened */ defaultTab?: string; /** Content of the header */ header: React.ReactNode; /** Body of the section */ children?: React.ReactNode; /** Children to display within the container */ overlay?: React.ReactNode; /** An optional key referencing a value in global state */ stateKey?: string; }) { const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, children, overlay, toggleOpenIcon = '▶', toggleCloseIcon = '▼', stateKey, } = props; const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState(); const tabFromState = stateKey && stateKey in syncedTabs ? tabs.find((tab) => tab.key === syncedTabs[stateKey]) : undefined; const [opened, setOpened] = React.useState(defaultOpened); const [selectedTabKey, setSelectedTab] = React.useState(tabFromState?.key ?? defaultTab); const selectedTab: InteractiveSectionTab | undefined = tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0]; return (
{ if (toggeable) { setOpened(!opened); } }} className={classNames('openapi-section-header', `${className}-header`)} >
{header}
{ event.stopPropagation(); }} > {tabs.length ? ( ) : null} {(children || selectedTab?.body) && toggeable ? ( ) : null}
{(!toggeable || opened) && (children || selectedTab?.body) ? (
{children} {selectedTab?.body}
) : null} {overlay}
); }