import React, { forwardRef, useContext, useRef, type JSX } from "react"; import { AriaTabPanelProps, useTab, useTabList, useTabPanel, } from "@react-aria/tabs"; import { useTabListState, Node } from "react-stately"; import { BaseCollection, Collection, CollectionBuilder, createHideableComponent, createLeafComponent, } from "@react-aria/collections"; import { useObjectRef } from "@react-aria/utils"; import { useContextProps } from "@hooks/useContextProps"; import { useRenderProps } from "@hooks"; import { useCollectionRenderer } from "@hooks/useCollectionRenderer"; import { Provider } from "@components/Internal/Provider"; import { ItemContext } from "@components/Collection"; import { TabContext, TabsContext, TabsStateContext } from "./Tabs.context"; import { TabListProps, TabProps, TabsProps } from "./Tabs.types"; import { TabContentWrapper, TabLink, TabLinksWrapper, StyledTabList, TabsWrapper, } from "./Tabs.Styles"; type ForwardedTabs = { ( props: TabsProps & React.RefAttributes ): JSX.Element; displayName: string; /** A list of Tabs in a Tabs component */ List: typeof TabList; /** A Tab in a Tabs component */ Tab: typeof TabWrapper; /** A panel that corresponds to a Tab in a Tabs component */ Panel: typeof TabPanel; }; export const Tabs = forwardRef(function Tabs( props: TabsProps, ref: React.ForwardedRef ) { [props, ref] = useContextProps(TabsContext, props, ref); return ( {(collection: BaseCollection) => ( )} ); }) as unknown as ForwardedTabs; Tabs.displayName = "Tabs"; interface TabsInnerProps extends TabsProps { collection: BaseCollection; tabsRef: React.RefObject; } export function TabsInner(props: TabsInnerProps) { const { tabsRef, id } = props; const state = useTabListState({ ...props, children: undefined, }); const renderProps = useRenderProps({ componentClassName: "aje-tabs", ...props, variant: props.variant || "default", }); return ( {props.children} ); } function TabList(props: TabListProps) { const state = useContext(TabsStateContext)!; // We're building the collection in the Tabs component if (!state) return ; // We're rendering a built collection return ; } TabList.displayName = "Tabs.List"; Tabs.List = TabList; function TabListInner(props: TabListProps) { const state = useContext(TabsStateContext)!; const ref = useRef(null); const { tabListProps } = useTabList(props, state, ref); const { CollectionRenderer } = useCollectionRenderer(); return ( } /> ); } function Tab( props: TabProps, ref: React.ForwardedRef, item: Node ) { [props, ref] = useContextProps(TabContext, props, ref); const state = useContext(TabsStateContext)!; const { key } = item; const { tabProps } = useTab({ key }, state, ref); const renderProps = useRenderProps({ componentClassName: "aje-tabs__tab", ...props, values: { isSelected: state.selectedKey === key, // TODO: Implement focus styles isFocused: false, isFocusVisible: false, }, selectors: { "data-selected": state.selectedKey === key, }, }); const Element = props.href ? "a" : "div"; return ( {renderProps.children} ); } const TabWrapper = createLeafComponent("item", Tab); // @ts-expect-error TabWrapper.displayName = "Tabs.Tab"; Tabs.Tab = TabWrapper; interface TabPanelProps extends AriaTabPanelProps { children?: React.ReactNode; } function TabPanelInner( props: TabPanelProps, forwardedRef: React.ForwardedRef ) { const state = useContext(TabsStateContext); const ref = useObjectRef(forwardedRef); const { tabPanelProps } = useTabPanel(props, state, ref); const isSelected = state?.selectedKey === props.id; if (!isSelected) return null; return ( {/* May contain other tabs, so clear out the providers below this in the tree */} {props.children} ); } const TabPanel = createHideableComponent(TabPanelInner); // @ts-expect-error TabPanel.displayName = "Tabs.Panel"; Tabs.Panel = TabPanel;